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", ¤tGroupTag ); 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.