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

This is Folder.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
	
	Class: Folder
	
	Description: See Folder.h

	Original Author: Jeremy Slade
	
	Revision History:
		Created
			V.101	JGS Wed Feb  3 23:26:29 GMT-0700 1993

*/

#import "Folder.h"

#import "FolderController.h"
#import "FolderViewer.h"
#import "Globals.h"
#import "Group.h"
#import "GroupBrowser.h"
#import "Inspector.h"
#import "ItemCell.h"

#import <libc.h>
#import <stdio.h>
#import <sys/dir.h>
#import <sys/param.h>
#import <sys/types.h>


// When frame.origin.x is set to NEEDS_NEW_VIEWER, the -obtainViewer
// method uses this to know that the folder has not yet been given a viewer.
#define NEEDS_NEW_VIEWER	(-100000)

#define MAX_TAG		(99999)


// This macro defines the conditions for when the FolderViewer will be
// marked as changed -- i.e. when the X in the close button will show
// as broken
#define LOOKS_CHANGED \
	(([changedGroups count] || (fFlags.removedGroup && [self count])) \
		? YES : NO)



@implementation Folder


// -------------------------------------------------------------------------
//   Creating, initializing Methods
// -------------------------------------------------------------------------


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



- initCount:(unsigned int)numSlots
{
	[super initCount:numSlots];
	
	currentGroup = nil;
	currentGroupTag = 0;
	filename = NULL;
	
	changedGroups = [[List alloc] initCount:0];
	
	frameRect.origin.x = NEEDS_NEW_VIEWER;
	viewerNum = 0;
	
	fFlags.isChanged = NO;
	fFlags.removedGroup = NO;
	fFlags.needsLoadBrowser = YES;
	fFlags.needsSort = NO;
	fFlags.needsShow = YES;
	fFlags.autoDisplay = YES;

	return ( self );
}



- free
{
	if ( filename ) NX_FREE ( filename );
	[changedGroups free];
	[viewer free];
	return ( [super free] );
}



// -------------------------------------------------------------------------
//   Filename
// -------------------------------------------------------------------------


- setFilename:(const char *)path
{
	if ( filename ) {
		if ( viewer ) [NXApp removeWindowsItem:viewer];
		NX_FREE ( filename );
	}
	filename = path ? NXCopyStringBuffer ( path ) : NULL;
	[self setNeedsShow:YES];
	return ( self );
}



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



// -------------------------------------------------------------------------
//   Folder Info
// -------------------------------------------------------------------------


- readInfo
/*
	Reads the folder info
*/
{
	NXTypedStream *stream;
	char infoPath[MAXPATHLEN+1];
	int	newCurrentGroupTag;
	int versionNumber;
	
	sprintf ( infoPath, "%s/%s", filename, FOLDER_INFO );
	
	if ( !(stream = NXOpenTypedStreamForFile ( infoPath, NX_READONLY ) ) ) {
		return ( nil ); // Unable to open stream to infoPath
	}
	
	// Get versionNumber
	NXReadType ( stream, "i", &versionNumber );
	
	// Read folder info according to versionNumber
	if ( versionNumber <= Folder_VERSION ) { // up thru current version
		NXReadTypes ( stream, "i", &newCurrentGroupTag );
		NXReadRect ( stream, &frameRect );
	}
	
	else {	// Unrecognized versionNumber
		[self errMsg:"Folder: Unrecognized version %i in Folder info!\n",
			versionNumber];
	}
	
	NXCloseTypedStream ( stream );

	fFlags.replaceGroups = NO; // Make sure this flag is off
	
	[self obtainViewer:nil];
	[self makeCurrentGroup:[self groupWithTag:newCurrentGroupTag]];
			
	return ( self );
}



- writeInfo
/*
	Write the folder's info to FOLDER_INFO
*/
{
	NXTypedStream *stream;
	char infoPath[MAXPATHLEN+1];
	int versionNumber;

	if ( !filename )
		return ( nil ); // No filename specified

	// Make sure the folder directory exists
	if ( access ( filename, F_OK ) != 0 ) {
		mkdir ( filename, DIR_CREATE_MASK ); // Make it
	}
		
	sprintf ( infoPath, "%s/%s", filename, FOLDER_INFO );	
	if ( !(stream = NXOpenTypedStreamForFile ( infoPath, NX_WRITEONLY ) ) ) {
		return ( nil ); // Couldn't open typed stream
	}

	[viewer getFrame:&frameRect];
	
	// Write the version number
	versionNumber = Folder_VERSION;
	NXWriteType ( stream, "i", &versionNumber );
	
	// Write the other folder info
	NXWriteTypes ( stream, "i", &currentGroupTag );
	NXWriteRect ( stream, &frameRect );
	
	NXCloseTypedStream ( stream );
	
	[self setChanged:NO];
	return ( self );
}



// -------------------------------------------------------------------------
//   Displaying
// -------------------------------------------------------------------------


- becomeKeyFolder
{
	DrawInfo info;
	
	// Enable the group popup-list so it responds to cmd-key events
	[[[viewer groupMenu] itemList] setEnabled:YES];
	
	// Set the currentGroups drawFlags to be active
	if ( currentGroup ) {
		[currentGroup getDrawInfo:&info];
		[ItemCell setDrawInfo:&info];
	}
	
	[self showSelf:self];
	return ( self );
}


- resignKeyFolder
/*
	Sent when this folder is no longer the keyFolder -- when something else is made the keyFolder.
*/
{
	// Disable cmd-keys on the group popup-list, so we no longer respond
	// to cmd-key events.
	[[[viewer groupMenu] itemList] setEnabled:NO];

	[self showSelf:self];
	return ( self );
}


- setNeedsShow:(BOOL)flag
{
	fFlags.needsShow = flag;
	if ( fFlags.autoDisplay )
		[self perform:@selector(showSelf:) with:self
			afterDelay:0 cancelPrevious:YES];
	return ( self );
}


- (BOOL)needsShow
{
	return ( fFlags.needsShow );
}


- setAutoDisplay:(BOOL)flag
{
	fFlags.autoDisplay = flag;
	if ( fFlags.autoDisplay && fFlags.needsShow ) [self showSelf:self];
	return ( self );
}



- (BOOL)isAutoDisplay
{
	return ( fFlags.autoDisplay );
}


- showSelf:sender
/*
	Display the viewer and all it's subviews
*/
{
	char path[MAXPATHLEN+1];
	
	// Make sure the folder has a viewer
	if ( !viewer ) [self obtainViewer:nil];
	
	// Only display if we need to...
	if ( !fFlags.needsShow ) return ( self );

//if ( DEBUGGING ) printf ( "Folder: showing...\n" );

	[viewer disableFlushWindow];
	
	// Sort the groups if needed
	if ( fFlags.needsSort ) [self sortGroups];
	
	// Make sure there is a current group, and display it's name in
	// the groupMenuCover button
	if ( ![self count] ) {
		[[[viewer groupMenuCover] setTitle:"No Groups"] setEnabled:NO];
	} else {
		if ( !currentGroup ) [self makeCurrentGroup:[self objectAt:0]];
		[[[viewer groupMenuCover] setTitle:[currentGroup groupName]]
			setEnabled:YES];
	}

	// Load the browser if needed
	if ( fFlags.needsLoadBrowser ) {
		[[viewer browser] showGroup:currentGroup];
		fFlags.needsLoadBrowser = NO;
		[[viewer browser] scrollToSelection]; // make sure the selected item is visible
	}
	
	[viewer setDocEdited:LOOKS_CHANGED];
	
	// Set the viewer's title to show the folder's filename
	if ( filename )
		[viewer setTitleAsFilename:filename];
	else {
		sprintf ( path, "UNTITLED" );
		[viewer setTitleAsFilename:path];
	}
	
	[viewer display];
	[[viewer reenableFlushWindow] flushWindow];
	
//if ( DEBUGGING ) printf ( "	Done.\n" );
	fFlags.needsShow = NO;
	[inspector update];
	return ( self );
}


#define HORIZ_STEP	24
#define	VERT_STEP	24
- obtainViewer:newViewer
/*
	If newViewer is not nil, replace the current viewer with the newViewer and return.  If folder doesn't already have a viewer, then loads a new Folder.nib to create a new FolderViewer, then sizes and locates it as appropriate.
*/
{
	static BOOL firstOne = YES;

	// This will always cause a need to be re-shown...
	[self setNeedsShow:YES];
	
	if ( newViewer && newViewer != viewer ) {
		[viewer free]; // Free the old viewer
		viewer = newViewer;
		viewerNum = [[newViewer delegate] viewerNum];
		[viewer setDelegate:self];
		fFlags.needsSort = YES;
	}

	if ( viewer ) { // Already has a viewer
		[viewer placeWindow:&frameRect];
		return ( self );
	}

	
	/*
		Create a new viewer
	*/
	
	// Load a new copy of the nib
	// viewer is automatically set to the loaded window
	[NXApp loadNibSection:"Folder.nib"
		owner:self withNames:NO
		fromZone:[self zone]];

	if ( frameRect.origin.x == NEEDS_NEW_VIEWER ) {
		if ( self != [folderController keyFolder] && !firstOne
				&& [folderController keyFolder] )
			[[[folderController keyFolder] viewer] getFrame:&frameRect];
		else
			frameRect.origin = defaultViewerFrame.origin;

		// Offset the new viewer from the last one opened
		if ( !firstOne ) {
			frameRect.origin.x += HORIZ_STEP;
			frameRect.origin.y += frameRect.size.height - VERT_STEP
				- defaultViewerFrame.size.height;
		}
		
		frameRect.size = defaultViewerFrame.size;
	}

	firstOne = NO;
	
	[viewer placeWindow:&frameRect];
	[viewer setDelegate:self];
	
	[viewer setHideOnDeactivate:hideDeactive];
	[self sortGroups];	
	NXConvertWinNumToGlobal ( [viewer windowNum], &viewerNum );
	return ( self );
}



- releaseViewer
/*
	Set viewer to nil, but don't free the actual viewer.  This is used when transfering the viewer to a different folder.  Returns our viewer
*/
{
	id ret = viewer;
	[viewer setDelegate:nil];
	viewer = nil;
	return ( ret );
}



- viewer
{
	return ( viewer );
}



- (int)viewerNum
{
	return ( viewerNum );
}



- setViewerFrame:(const NXRect *)newFrame
{
	frameRect = *newFrame;
	[self setChanged:YES];
	return ( self );
}



- updateInspector:sender
/*
	Sent by the Inspector when it needs to be updated
*/
{
	switch ( [inspector inspectorMode] ) {
		case INSPECT_FOLDER:
			[inspector inspect:self]; break;
		case INSPECT_GROUP:
			[inspector inspect:currentGroup]; break;
		case INSPECT_ITEM:
			[inspector inspect:[[viewer browser] selection]]; break;
		default: break;
	}
	return ( self );
}

	

// -------------------------------------------------------------------------
//   Change flag
// -------------------------------------------------------------------------


- setChanged:(BOOL)flag
/*
	Set fFlags.isChanged to flag
*/
{
	fFlags.isChanged = flag;

	if ( fFlags.isChanged && filename ) {
		[self writeInfo];
		fFlags.isChanged = NO;
	}
	[viewer setDocEdited:LOOKS_CHANGED];
	return ( self );
}



- (BOOL)isChanged
{
	return ( LOOKS_CHANGED );
}



- groupChanged:aGroup
{
	[changedGroups addObjectIfAbsent:aGroup];
	[viewer setDocEdited:LOOKS_CHANGED];
	return ( self );
}



- groupSaved:aGroup
{
	[changedGroups removeObject:aGroup];
	[viewer setDocEdited:LOOKS_CHANGED];
	return ( self );
}



- groupRemoved
{
	fFlags.removedGroup = YES;
	[viewer setDocEdited:LOOKS_CHANGED];
	return ( self );
}



- setNeedsLoadBrowser:(BOOL)flag
{
	fFlags.needsLoadBrowser = flag;
	[self setNeedsShow:YES];
	return ( self );
}



- (BOOL)needsLoadBrowser
{
	return ( fFlags.needsLoadBrowser );
}



- replaceAllGroups
/*
	Sets the replaceGroups flag, which will cause all group files in the folder directory to first be removed before the current versions are written the next time the folder is saved.
*/
{
	fFlags.replaceGroups = YES;
	return ( self );
}



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


- readGroups
/*
	Read all the group files in the folder directory and add them to self
*/
{
	struct direct **namelist;
	int i, count;
	int selectGroups ( struct direct *dp );
	NXTypedStream *stream;
	id group;
	char path[MAXPATHLEN+1];
	
	if ( !filename )
		return ( self );
		
	count = scandir ( filename, &namelist, selectGroups, NULL );
	for ( i=0; i<count; i++ ) {
		sprintf ( path, "%s/%s", filename, namelist[i]->d_name );
		if ( (stream = NXOpenTypedStreamForFile ( path, NX_READONLY ) ) ) {
			if ( (group = NXReadObject ( stream ) ) ) {
				[self addObject:group];
				[group setFolder:self];
			} else {
				[self errMsg:"Folder: unable read archived group from\n\t%s\n",
					path];
			}
			NXCloseTypedStream ( stream );
		} else {
			[self errMsg:"Folder: unable to open stream to\n\t%s\n", path];
		}
		free ( namelist[i] );
	}
	
	if ( count != -1 ) free ( namelist );

	fFlags.replaceGroups = NO; // Make sure this flag is off
	fFlags.needsSort = YES; // Force groups to be sorted
	[self sortGroups];
	
	[self setNeedsShow:YES];
	return ( self );
}



- writeGroups
/*
	Write all the groups to files in the folder directory
*/
{
	char rm_groups[MAXPATHLEN+1];
	
	[self writeInfo]; // Always do this when writing groups...
	
	if ( fFlags.replaceGroups ) {
		// Remove all groups before writing current groups
		sprintf ( rm_groups, "/bin/rm -f %s/*.%s", filename, GROUP_EXT );
		system ( rm_groups ); // Remove all the existing group files
		fFlags.replaceGroups = NO; // Make sure it is off now
	}

	[self makeObjectsPerform:@selector(writeSelf)];
	
	fFlags.removedGroup = NO;
	[viewer setDocEdited:LOOKS_CHANGED];
	return ( self );
}



- (int)tagFor:group
{
	int newTag = [group tag];
	
	srand ( time ( 0 ) );
	while ( !newTag && [self groupWithTag:newTag] != group ) {
		newTag = (rand() % MAX_TAG) + 1; // Choose a random tag
	}
	return ( newTag );
}



- groupWithTag:(int)tag
{
	int i, count;
	id group;
	
	count = [self count];
	for ( i=0; i<count; i++ ) {
		if ( [(group = [self objectAt:i]) tag] == tag )
			return ( group );
	}
	return ( nil ); // Didn't find a group with that tag
}



- groupMenu
{
	return ( [viewer groupMenu] );
}



- currentGroup
{
	return ( currentGroup );
}



- newGroup:sender
/*
	Create a new group and make it the currentGroup
*/
{
	int i;
	char name[20];
	
	id newGroup = [Group new];
	
	// Determine a new unique name
	i = 1;
	do
		sprintf ( name, "UNNAMED%d", i++ );
	while ( [self groupCalled:name] );
	
	[newGroup setGroupName:name];
	[newGroup setFolder:self];
	
	fFlags.needsSort = YES;
	[self setNeedsShow:YES];
	
	[self addObject:newGroup];
	[self groupChanged:newGroup];
	[self makeCurrentGroup:newGroup];

	// Bring up inspector on new group and select the group name field
	[inspector setInspectorMode:INSPECT_GROUP];
	[inspector inspect:newGroup];
	[[inspector panel] makeKeyWindow];
	[inspector selectGroupName:self];
	
	return ( self );
}



- deleteCurrentGroup:sender
/*
	Delete the currentGroup
*/
{
	int i;
	id group = currentGroup;
	char path[MAXPATHLEN+1];
	
	// Always verify this operation, since it is irreversible
	if ( NXRunAlertPanel ( "Verify",
		"This action is irreversible.  Are you sure you want to delete the group \"%s\" ?",
		"Delete", "Cancel", NULL, [currentGroup groupName] )
			!= NX_ALERTDEFAULT )
		return ( self );
	
	i = [self indexOf:currentGroup];
	[self removeObject:currentGroup];
	[[viewer browser] showGroup:nil];
	[self setNeedsShow:YES];

	[self sortGroups];
	[self setChanged:YES];
	
	if ( [self count] ) {
		if ( i>0 ) i--;
		[self makeCurrentGroup:[self objectAt:i]];
	} else
		[self makeCurrentGroup:nil];
	
	// Remove the group from the folder
	if ( filename ) {
		sprintf ( path, "%s/%s.%s", filename, [group groupName], GROUP_EXT );
		unlink ( path );
	}

	[changedGroups removeObject:group];
	[self groupRemoved];
	[group free];
		
	return ( self );
}



- launchCurrentGroup:sender
/*
	Launch all items in currentGroup that are tagged for groupLaunch
*/
{
	int i, count;
	
	[NXApp deactivateSelf];
	
	for ( i=0, count=[currentGroup count]; i<count; i++ )
		if ( [[currentGroup objectAt:i] isGroupLaunch] )
			[[currentGroup objectAt:i] launch];
	
	// Make sure the cells get redrawn
	[self setNeedsShow:YES];	
	return ( self );
}



- cleanUpCurrentGroup:sender
{
	// Forward to current group
	[currentGroup cleanUp:sender];
	return ( self );
}



- groupCalled:(const char *)groupName
{
	int i, count;
	
	if ( !groupName )
		return ( nil );

	for ( i=0, count=[self count]; i<count; i++ )
		if ( !strcmp ( [[self objectAt:i] groupName], groupName ) )
			return ( [self objectAt:i] );
			
	return ( nil );
}



- (BOOL)groupExists:(const char *)groupName
{
	return ( [self groupCalled:groupName] ? YES : NO );
}



- renameGroup:aGroup to:(const char *)aString
{
	id g;
	char path[MAXPATHLEN+1];
	
	if ( (g =[self groupCalled:aString]) && g != aGroup )
		return ( nil ); // This name is already in use

	// Remove the old group file
	if ( filename ) {
		sprintf ( path, "%s/%s.%s", filename, [aGroup groupName], GROUP_EXT );
		unlink ( path );
	}
	
	// Rename the group
	[aGroup setGroupName:aString];
	[aGroup setChanged:YES];
		
	fFlags.needsSort = YES; // Force groups to be sorted
	[self sortGroups];
	
	if ( aGroup == currentGroup && [viewer isVisible] ) {
		[[viewer groupMenuCover] setTitle:aString];
		[viewer display];
	}
		
	return ( self );
}



- selectGroupFromMenu:sender
{
	[self makeCurrentGroup:[self groupCalled:[[viewer groupMenu] selectedItem]]];
	return ( self );
}



- makeCurrentGroup:aGroup
{
	id oldGroup;
	
	if ( aGroup == currentGroup ) // No change
		return ( self );

	oldGroup = currentGroup;
	currentGroup = aGroup;

	[folderController updateMenu:self];

	if ( currentGroup ) currentGroupTag = [currentGroup tag];
		else currentGroupTag = 0;
	
	if ( [currentGroup drawMode] == IC_DRAW_UNKNOWN )
		[currentGroup setDrawMode:IC_LARGE_BROWSE];
		
	fFlags.needsLoadBrowser = YES;
	[self setNeedsShow:YES];
	
	if ( currentGroup != oldGroup && oldGroup != nil )  [self setChanged:YES];
	[self updateInspector:self];
	return ( self );
}



int compareGroups ( Group **group1, Group **group2 )
/*
	Compare the two groups for sorting.  First, compares their key equivalents, then compares them alphabetically.  Returns less than 0 if group1<group2, 0 if group1==group2, greater than 0 if group1>group2.
	THIS METHOD IS CURRENTLY BROKEN
*/
{
	int compare = 0;
	
	if ( [*group1 keyEquivalent] < [*group2 keyEquivalent] ) {
		if ( ![*group1 keyEquivalent] ) compare = 1; // Group1 > Group2
			else compare = -1; // Group1 < Group2
	} else
	
	if ( [*group1 keyEquivalent] > [*group2 keyEquivalent] ) {
		compare = 1; // Group1 > Group2
	} else
	
	if ( [*group1 keyEquivalent] == [*group2 keyEquivalent] ) {
		compare = NXOrderStrings ( [*group1 groupName], [*group2 groupName],
					NO, -1, NULL );\
	}

	return ( compare );
}



- sortGroups
{
	int i;
	id	groupMenu = [viewer groupMenu];
		
	// Remove all items from [viewer groupMenu]
	while ( [[[groupMenu itemList] cellList] count] ) [groupMenu removeItemAt:0];
		
	if ( fFlags.needsSort ) {
		// qsort ( dataPtr, numElements, sizeof ( id * ), compareGroups );
		// qsort() seems to choke on something, so we use our own buble sort:
		// (this is ok since the number of groups is generally small)
		int j,k, compare;
		id temp;
		for ( j=0; j<numElements; j++ ) {
			for ( k=0; k<numElements-1; k++ ) {
				compare = compareGroups ( (dataPtr + k), (dataPtr + k+1) );
				if ( compare > 0 ) { // group k > group k+1
					// Do nothing -- already in proper order
				} else
				if ( compare == 0 ) { // group k = group k+1
					// Do nothing -- already in proper order
				} else
				if ( compare < 0 ) { // group k < group k+1
					// Swap group k and group k+1
					temp = *(dataPtr + k);
					*(dataPtr + k) = *(dataPtr + k+1);
					*(dataPtr + k+1) = temp;
				}
			}
		}	
		fFlags.needsSort = NO;
	}
	
	i = numElements;
	while ( i-- ) { // Add the groups to the menu in reverse order
		[groupMenu addItem:[[self objectAt:i] groupName]
			action:@selector(selectGroupFromMenu:)
			keyEquivalent:[[self objectAt:i] keyEquivalent]];
	}
	[groupMenu sizeToFit];
	return ( self );
}



// -------------------------------------------------------------------------
//   Responding to key events
// -------------------------------------------------------------------------


- keyDown:(NXEvent *)event
/*
	Respond to the user presssing a key.  If the key can be handled by the folder, returns self.  If the key has no meaning to the folder, returns nil.
*/
{
	int i = 0;
	
	if ( event->data.key.charSet == NX_ASCIISET && !event->flags ) {
		switch ( event->data.key.charCode ) {
			case NX_CR:
			case NX_RETURN:
				[currentGroup launchSelectedItems:self];
				return ( self );
				
			case NX_DELETE: // Delete removes selected items
				[currentGroup removeSelectedItems:self];
				return ( self );
		}
	} else
	
	if ( event->data.key.charSet == NX_ASCIISET
		&& event->flags == NX_NUMERICPADMASK
		&& event->data.key.charCode == 0x03 ) { // Enter key on key pad
			[currentGroup launchSelectedItems:self];
			return ( self );
	}
	
	else {
		switch ( event->data.key.charCode ) {
		
			case 172: // Left arrow -- move to previous group
				if ( (i=[self indexOf:currentGroup]) < numElements-1 )
					[self makeCurrentGroup:[self objectAt:++i]];
				return ( self );
				
			case 174:	// Right arrow -- move to next group
				if ( (i=[self indexOf:currentGroup]) > 0 )
					[self makeCurrentGroup:[self objectAt:--i]];
				return ( self );
				
			case 173:	// Up arrow -- move selection up
				[[viewer browser] selectUp:self];
				return ( self );
			
			case 175: // Down arrow -- move selection down
				[[viewer browser] selectDown:self];
				return ( self );
		}
		
	}
	
	return ( nil );
}	



// -------------------------------------------------------------------------
//   Window Delegate methods -- sent by our Viewer
// -------------------------------------------------------------------------


- windowWillClose:sender
{
	int ret;
	
	// Ask to save the Folder if it has been changed before closin it
	if ( [self isChanged] ) {
		ret = NXRunAlertPanel ( "Alert",
			"Folder: %s hasn't been saved.  Do you want to save it?",
			"Save", "Don't Save", "Cancel", filename ? filename : "UNTITLED" );
		switch ( ret ) {
			case NX_ALERTDEFAULT: // Save
				[folderController saveFolder:self];
				break;
			case NX_ALERTALTERNATE: // Don't Save
				break;
			case NX_ALERTOTHER: // Cancel
				return ( nil );
		}
	}

	// Remove the folder only once we get back to the event loop...
	// (in case anybody is still going to send messages to it)
	[folderController perform:@selector(removeFolder:)
		with:self
		afterDelay:0
		cancelPrevious:NO];

	return ( self );
}



#define VIEWER_MIN_W	100
#define VIEWER_MIN_H	145
- windowWillResize:sender toSize:(NXSize *)size
{
	if ( size->width < VIEWER_MIN_W ) size->width = VIEWER_MIN_W;
	if ( size->height < VIEWER_MIN_H ) size->height = VIEWER_MIN_H;
	return ( self );
}



- windowDidBecomeKey:sender
{
	if ( [NXApp mainWindow] != sender ) {
		[[NXApp mainWindow] resignMainWindow];
		[sender becomeMainWindow];
	}
	[folderController makeKeyFolder:self];
	return ( self );
}



- windowDidResignKey:sender
{
	return ( self );
}



- windowDidMiniaturize:sender
{
	return ( self );
}



- windowDidMove:sender
{
	NXRect frame;
	[sender getFrame:&frame];
	[self setViewerFrame:&frame];
	return ( self );
}



- windowDidResize:sender
{
	NXRect frame;
	[sender getFrame:&frame];
	[self setViewerFrame:&frame];
	return ( self );
}



// -------------------------------------------------------------------------
//   GroupBrowser Delegate methods
// -------------------------------------------------------------------------


- browserSelectionChanged:sender
{
	if ( inspectorMode == INSPECT_ITEM ) [inspector update];
	return ( self );
}



@end



// selectGroups() function used by scandir in -readGroups
int selectGroups ( struct direct *dp )
/*
	Returns 1 if dp->d_name is a group file, 0 if not
*/
{
	char *ext;
	
	ext = rindex ( dp->d_name, '.' ); // find last '.'
	if ( !ext ) return ( 0 ); // This one doesn't have any extension
		else ext++; // extension starts right after last '.'
		
	return ( !strcmp ( ext, GROUP_EXT ) ? 1 : 0 );
}


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