home *** CD-ROM | disk | FTP | other *** search
/ Nebula 1995 August / NEBULA.mdf / Apps / Utilities / Desktop / Locus / Source / FolderController.m < prev    next >
Encoding:
Text File  |  1993-05-24  |  27.4 KB  |  1,198 lines

  1.  
  2. /*
  3.     Copyright 1993  Jeremy Slade.
  4.  
  5.     You are free to use all or any parts of the Locus project
  6.     however you wish, just give credit where credit is due.
  7.     The author (Jeremy Slade) shall not be held responsible
  8.     for any damages that result out of use or misuse of any
  9.     part of this project.
  10.  
  11. */
  12.  
  13. /*
  14.     Project: Locus
  15.  
  16.     File: FolderController.m
  17.  
  18.     Description: See FolderController.h
  19.  
  20.     Original Author: Jeremy Slade
  21.  
  22.     Revision History:
  23.         Created
  24.             V.101    JGS Tue Feb  2 19:28:05 GMT-0700 1993
  25.  
  26. */
  27.  
  28. #import "FolderController.h"
  29.  
  30. #import "Activator.h"
  31. #import "AppIconView.h"
  32. #import "AtomList.h"
  33. #import "DynamicItems.h"
  34. #import "Folder.h"
  35. #import "FolderViewer.h"
  36. #import "Globals.h"
  37. #import "Group.h"
  38. #import "GroupBrowser.h"
  39. #import "GroupBrowserMatrix.h"
  40. #import "Inspector.h"
  41. #import "SwapView.h"
  42.  
  43. #import <libc.h>
  44. #import <mach/mach.h>
  45. #import <objc/List.h>
  46. #import <objc/NXStringTable.h>
  47. #import <stdio.h>
  48. #import <streams/streams.h>
  49. #import <sys/file.h>
  50. #import <sys/param.h>
  51. #import <sys/stat.h>
  52. #import <sys/types.h>
  53.  
  54.  
  55. // Item Pasteboard
  56. #define ITEM_PBOARD    "_ItemPasteboard_"
  57.  
  58.  
  59. @implementation FolderController
  60.  
  61.  
  62. // -------------------------------------------------------------------------
  63. //   Creating, initializing methods
  64. // -------------------------------------------------------------------------
  65.  
  66.  
  67. + initialize
  68. {
  69.     [self setVersion:FolderController_VERSION];
  70.     return ( self );
  71. }
  72.  
  73.  
  74.  
  75. - init
  76. {
  77.     [super init];
  78.     
  79.     itemPboard = [Pasteboard newName:ITEM_PBOARD];
  80.     if ( !folderList ) folderList = [[List alloc] initCount:0];
  81.     keyFolder = nil;
  82.     
  83.     return ( self );
  84. }
  85.  
  86.  
  87.  
  88. - free
  89. {
  90.     [[folderList freeObjects] free];
  91.     [itemPboard free];
  92.     return ( [super free] );
  93. }
  94.  
  95.  
  96.  
  97. // -------------------------------------------------------------------------
  98. //    Activating
  99. // -------------------------------------------------------------------------
  100.  
  101.  
  102. - setup
  103. /*
  104.     Setup the FolderController.  The actions performed here are as follows:
  105.         1) Check for existance of LIBRARY_DIR, create it if necessary
  106.         2) Check for existance of ICONS_DIR, create it if necessary
  107.         3) Open the startup Folders
  108. */
  109. {
  110.     char folderDir[MAXPATHLEN+1], iconPath[MAXPATHLEN+1];
  111.     const char *path;
  112.     int i, count;
  113.     
  114.     if ( isSetup )    // Setup has already been performed
  115.         return ( self );
  116.         
  117.     /* Check for Library directory, make it if not found */
  118.     sprintf ( folderDir, "%s/%s", NXHomeDirectory(), LIBRARY_DIR );
  119.     if ( access ( folderDir, F_OK ) == -1 )
  120.         mkdir ( folderDir, DIR_CREATE_MASK );
  121.  
  122.     /* Also check for the Icons directory */
  123.     sprintf ( iconPath, "%s/%s/%s",
  124.         NXHomeDirectory(), LIBRARY_DIR, ICONS_DIR );
  125.     if ( access ( iconPath, F_OK ) == -1 )
  126.         mkdir ( iconPath, DIR_CREATE_MASK );
  127.     
  128.     [appIconView attachToIcon:[NXApp appIcon]];
  129.     [appIconView setDraggingEnabled:appIconAdd];
  130.     
  131.     /* Open the startup folders */
  132.     count = [startupFolders count];
  133.     for ( i=0; i<count; i++ ) {
  134.         path = [self resolvePathForFolder:[startupFolders atomAt:i]];
  135.         if ( ![self openFolder:path]
  136.                 && ![self folderWithFilename:path] ) {
  137.             // Error opening the folder
  138.             NXRunAlertPanel ( "Startup", "Unable to open startup folder: %s",
  139.                 "Ok", NULL, NULL, path );
  140.         }
  141.     }
  142.         
  143.     isAutoLaunch = NO;    // Turn this off after initial setup
  144.     isSetup = YES;
  145.     
  146.     return ( self );
  147. }
  148.  
  149.  
  150.  
  151. - (BOOL)cleanup
  152. /*
  153.     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:
  154. */
  155. {
  156.     int i, count, ret;
  157.     id    folder;
  158.     
  159.     // Check for unsaved folders
  160.     count=[folderList count];
  161.     for ( i=0; i<count; i++ ) {
  162.         folder = [folderList objectAt:i];
  163.         if ( [folder isChanged] ) {
  164.             ret = NXRunAlertPanel ( "Warning",
  165.                 "%s has been changed.  Do you want to save it?",
  166.                 "Save", "Don't Save", "Cancel", [folder filename] );
  167.             switch ( ret ) {
  168.                 case NX_ALERTDEFAULT: // Save
  169.                     [self writeFolder:folder];
  170.                     break;
  171.                 case NX_ALERTALTERNATE: // Don't Save
  172.                     break;
  173.                 case NX_ALERTOTHER: // Cancel
  174.                     return ( NO );
  175.                     
  176.             }
  177.         }
  178.     }
  179.  
  180.     [appIconView detachFromIcon];
  181.  
  182.     return ( YES );
  183. }
  184.  
  185.  
  186.  
  187. - active:sender;
  188. /*
  189.     Called when Locus is activated (becomes the active app).  Set the App Icon to show an active state.
  190. */
  191. {
  192.     if ( ![NXApp isActive] ) [NXApp activateSelf:NO];
  193.     
  194.     [appIconView setIcon:"ActiveIcon"];
  195.     if ( ![[[NXApp keyWindow] delegate] isKindOf:[Folder class]] )
  196.         [[keyFolder viewer] makeKeyWindow];
  197.     
  198.     return ( self );
  199. }
  200.  
  201.  
  202.  
  203. - inactive:sender
  204. /*
  205.     Called after Locus is deactived -- is not longer the active app.  Set the App Icon to show an inactive state.
  206. */
  207. {
  208.     [appIconView setIcon:"InactiveIcon"];
  209.     return ( self );
  210. }
  211.  
  212.  
  213.  
  214. - doAutoLaunch:sender
  215. /*
  216.     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.
  217. */
  218. {
  219.     int grp, gcount, e, ecount;
  220.     id group, item;
  221.     
  222.     if ( !keyFolder )
  223.         return ( self );
  224.  
  225.     gcount=[keyFolder count];
  226.     for ( grp=0; grp<gcount; grp++ ) {
  227.         group = [keyFolder objectAt:grp];
  228.         ecount=[group count];
  229.         for ( e=0; e<ecount; e++ ) {
  230.             item = [group objectAt:e];
  231.             if ( [item isAutoLaunch] ) {
  232.                 [item launch];
  233.             }
  234.         }
  235.     }
  236.     
  237.     return ( self );
  238. }
  239.  
  240.  
  241.  
  242. // -------------------------------------------------------------------------
  243. //   File operations
  244. // -------------------------------------------------------------------------
  245.  
  246.  
  247. - (const char *)resolvePathForFolder:(const char *)path
  248. /*
  249.     Resolves a relative path to a Folder into a full path.  See -relativePathForFolder: for more info.
  250. */
  251. {
  252.     static char resolvedPath[MAXPATHLEN+1];
  253.     
  254.     if ( !path ) return ( NULL );
  255.     
  256.     if ( path[0] == '/' ) { // Full path name specified, no resolving necessary
  257.         strcpy ( resolvedPath,  path );
  258.     } else
  259.     if ( path[0] == '~' ) { // Relative to User's home dir
  260.         sprintf ( resolvedPath, "%s%s", NXHomeDirectory(), &path[1] );
  261.     } else { //  Assume it is in LIBRARY_DIR
  262.         sprintf ( resolvedPath, "%s/%s/%s", NXHomeDirectory(), LIBRARY_DIR, path );
  263.     }
  264.     
  265.     return ( resolvedPath );
  266. }
  267.  
  268.  
  269.  
  270. - (const char *)relativePathForFolder:(const char *)path
  271. /*
  272.     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.
  273. */
  274. {
  275.     static char relativePath[MAXPATHLEN+1];
  276.     const char *p, *homeDir = NXHomeDirectory();
  277.     
  278.     if ( !path ) return ( NULL );
  279.     
  280.     if ( !strncmp ( path , homeDir, strlen ( homeDir ) ) ) {
  281.         // The folder is somewhere in the user's home dir
  282.         p = path + strlen ( homeDir ) + 1; // +1 to get past the '/'
  283.         if ( strstr ( p, LIBRARY_DIR ) == p ) {
  284.             // The folder is in the LIBRARY_DIR
  285.             p += strlen ( LIBRARY_DIR ) + 1; // +1 to get past last '/'
  286.             strcpy ( relativePath, p );
  287.         } else {
  288.             // The folder is somewhere other than in LIBRARY_DIR
  289.             sprintf ( relativePath, "~/%s", p );
  290.         }
  291.     } else {
  292.         // Folder is not anywhere in user's home dir
  293.         strcpy ( relativePath, path );
  294.     }
  295.     
  296.     return ( relativePath );
  297. }
  298.  
  299.  
  300.  
  301. - readFolderFromPath:(const char *)path
  302. /*
  303.     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.
  304. */
  305. {
  306.     id    folder;
  307.     
  308.     if ( isSetup && access ( path, F_OK | R_OK ) != 0 )
  309.         return ( nil );
  310.  
  311.     folder = [Folder new];
  312.     [folder setFilename:path];
  313.     [folder readGroups];
  314.     [folder readInfo];
  315.     
  316.     return ( folder );    
  317. }
  318.  
  319.  
  320.  
  321. - writeFolder:aFolder
  322. /*
  323.     Save the Folder object by writing both the Folder info and the Groups. The Folder is saved to the path specified by [aFolder filename]
  324. */
  325. {
  326.     [aFolder writeInfo];
  327.     [aFolder writeGroups];
  328.     return ( self );
  329. }
  330.  
  331.  
  332.         
  333. - openFolder:(const char *)path
  334. /*
  335.     Open the folder specified by path and add it to folderList
  336. */
  337. {
  338.     id    newFolder;
  339.     
  340.     if ( !(newFolder = [self readFolderFromPath:path]) )
  341.         return ( nil ); // Unable to read the Folder at path
  342.     
  343.     if ( ![self addFolder:newFolder] ) {
  344.         // Unable to add the Folder to our folderList
  345.         [newFolder free];
  346.         return ( nil );
  347.     }
  348.     
  349.     [newFolder showSelf:self];
  350.     [self makeKeyFolder:newFolder];
  351.     if ( isAutoLaunch ) [self doAutoLaunch:self];
  352.         // Perform autoLaunch only at launch-time
  353.     
  354.     return ( self );
  355. }
  356.  
  357.  
  358.  
  359. - openFolderFromOpenPanel:sender
  360. /*
  361.     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).
  362. */
  363. {
  364.     id openPanel = [OpenPanel new];
  365.     static char directory[MAXPATHLEN+1] = "";
  366.     const char *const types[2] = { FOLDER_EXT, NULL };
  367.     char path[MAXPATHLEN+1];
  368.     const char *const *files;
  369.     int i;
  370.     
  371.     if ( !strlen ( directory ) )
  372.         sprintf ( directory, "%s/%s", NXHomeDirectory(), LIBRARY_DIR );
  373.     
  374.     [[openPanel setTitle:"Open Folder"] setPrompt:"Folder:"];
  375.     [openPanel allowMultipleFiles:YES];
  376.  
  377.     if ( [openPanel runModalForDirectory:directory file:NULL types:types] ) {
  378.         strcpy ( directory, [openPanel directory] ); // Save directory
  379.         files = [openPanel filenames];
  380.         i = 0;
  381.         while ( files[i] ) {
  382.             sprintf ( path, "%s/%s", directory, files[i] );
  383.             [self openFolder:path];
  384.             i++;
  385.         }
  386.     }
  387.         
  388.     return ( self );
  389. }
  390.  
  391.  
  392.  
  393. - newFolder:sender
  394. /*
  395.     Create a new, un-titled Folder, add it to the Folder list
  396. */
  397. {
  398.     id newFolder = [[Folder new] obtainViewer:nil];
  399.     if ( ![self addFolder:newFolder] ) [newFolder free];
  400.         else [self makeKeyFolder:newFolder];
  401.     return ( newFolder );
  402. }
  403.  
  404.  
  405.  
  406. - saveKeyFolder:sender
  407. {
  408.     [self saveFolder:keyFolder];
  409.     return ( self );
  410. }
  411.  
  412.  
  413.  
  414. - saveKeyFolderAs:sender
  415. {
  416.     [self saveFolderAs:keyFolder];
  417.     return ( self );
  418. }
  419.  
  420.  
  421.  
  422. - saveAllFolders:sender
  423. /*
  424.     Go through the folderList and save all folders that have been changed.
  425. */
  426. {
  427.     int i, count;
  428.     count = [folderList count];
  429.     for ( i=0; i<count; i++ )
  430.         if ( [[folderList objectAt:i] isChanged] )
  431.             [self saveFolder:[folderList objectAt:i]];
  432.     return ( self );
  433. }
  434.  
  435.  
  436.  
  437. - revertKeyFolder:sender
  438. /*
  439.     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.
  440. */
  441. {
  442.     id revertedFolder;
  443.     
  444.     if ( ![keyFolder filename] ) {
  445.         // Don't do anything if the Folder has never been saved
  446.         NXBeep();
  447.         return ( self );
  448.     }
  449.     
  450.     if ( !(revertedFolder = [self readFolderFromPath:[keyFolder filename]]) ) {
  451.         NXRunAlertPanel ( "Revert", "Unable to restore the Folder.",
  452.             NULL, NULL, NULL );
  453.         return ( self );
  454.     }
  455.     
  456.     [self replaceFolder:keyFolder with:revertedFolder];
  457.     return ( self );
  458. }
  459.  
  460.  
  461.  
  462. // -------------------------------------------------------------------------
  463. //   Folders
  464. // -------------------------------------------------------------------------
  465.  
  466.  
  467. - keyFolder
  468. {
  469.     return ( keyFolder );
  470. }
  471.  
  472.  
  473.  
  474. - (BOOL)addFolder:aFolder
  475. /*
  476.     Add aFolder to folderList if it isn't already there, then make it the keyFolder.
  477. */
  478. {
  479.     if ( [self folderLoaded:[aFolder filename]] ) {
  480.         // Activate the exisiting one of it is there
  481.         [self makeKeyFolder:[self folderWithFilename:[aFolder filename]]];
  482.         return ( NO );
  483.     }
  484.     
  485.     [folderList addObject:aFolder];
  486.     return ( YES );
  487. }
  488.  
  489.  
  490.  
  491. - saveFolder:aFolder
  492. {
  493.     if ( [aFolder filename] ) [self writeFolder:aFolder];
  494.         else [self saveFolderAs:aFolder];
  495.     return ( self );
  496. }
  497.  
  498.  
  499.  
  500. - saveFolderAs:aFolder
  501. {
  502.     id savePanel = [SavePanel new];
  503.     static char directory[MAXPATHLEN+1] = "";
  504.     const char *filename;
  505.     
  506.     if ( !strlen ( directory ) ) 
  507.         sprintf ( directory, "%s/%s", NXHomeDirectory(), LIBRARY_DIR );
  508.         
  509.     [[savePanel setTitle:"Save Folder"] setPrompt:"Folder:"];
  510.     [savePanel setRequiredFileType:FOLDER_EXT];
  511.     
  512.     if ( [savePanel runModalForDirectory:directory file:NULL] ) {
  513.         strcpy ( directory, [savePanel directory] );
  514.         filename = [savePanel filename];
  515.         [aFolder setFilename:filename];
  516.         [aFolder replaceAllGroups]; // Overwrite any existing groups
  517.         [self writeFolder:aFolder];
  518.     }
  519.     
  520.     [aFolder showSelf:self];
  521.     return ( self );
  522. }
  523.  
  524.  
  525.  
  526. - removeFolder:aFolder
  527. /*
  528.     Remove the folder from folderList.  This happens when the Folder gets closed.
  529. */
  530. {
  531.     int i;
  532.     
  533.     i = [folderList indexOf:aFolder];
  534.     if ( i == NX_NOT_IN_LIST )
  535.         return ( nil ); // The folder is not in the list -- shouldn't happen
  536.         
  537.     [folderList removeObjectAt:i];
  538.     
  539.     // Choose the Folder to be made the keyFolder after this one is removed.
  540.     if ( i>0 ) i--;
  541.     if ( keyFolder == aFolder )
  542.         if ( [folderList count] )
  543.             [self makeKeyFolder:[folderList objectAt:i]];
  544.         else
  545.             [self makeKeyFolder:nil];
  546.         
  547.     [self updateDisplay];
  548.     [aFolder free];
  549.     return ( self );
  550. }
  551.  
  552.  
  553.  
  554. - replaceFolder:aFolder with:newFolder
  555. /*
  556.     Replace aFolder with newFolder, assigning aFolder's viewer/window to newFolder.  This is used when reverting a changed Folder.
  557. */
  558. {
  559.     BOOL wasKeyFolder;
  560.     
  561.     if ( keyFolder == aFolder ) wasKeyFolder = YES;
  562.         else wasKeyFolder = NO;
  563.  
  564.     [folderList replaceObject:aFolder with:newFolder];
  565.     [newFolder replaceAllGroups]; // Make sure existing groups are overwritten
  566.     
  567.     [newFolder obtainViewer:[aFolder releaseViewer]];
  568.     [newFolder showSelf:self];
  569.  
  570.     if ( wasKeyFolder ) {
  571.         keyFolder = newFolder;
  572.         [self makeKeyFolder:keyFolder];
  573.     }
  574.         
  575.     [aFolder free];
  576.     return ( self );
  577. }
  578.  
  579.  
  580.  
  581. - makeKeyFolder:aFolder
  582. /*
  583.     Makes aFolder the keyFolder, and it's viewer the keyWindow
  584. */
  585. {
  586.     // Only do this stuff when the keyFolder is actually changing
  587.     if ( keyFolder != aFolder ) {
  588.         [[keyFolder viewer] resignMainWindow];
  589.             // This will send resignKeyFolder
  590.         keyFolder = aFolder;
  591.         [self updateMenu:self];
  592.     }
  593.     
  594.     // This will make the Folder's viewer the main and key window,
  595.     // and the folder will be sent -becomeKeyFolder
  596.     [[[[keyFolder viewer] orderFront:self] becomeMainWindow] makeKeyWindow];
  597.     
  598.     if ( keyFolder ) [keyFolder updateInspector:self];
  599.         else [inspector inspect:nil];
  600.     return ( self );
  601. }
  602.  
  603.  
  604.  
  605. - folderWithFilename:(const char *)path
  606. /*
  607.     Searches the folderList for a folder with filename = path, returns it if found, nil otherwise.
  608. */
  609. {
  610.     int i, count;
  611.     
  612.     if ( !path )
  613.         return ( nil );
  614.     
  615.     count=[folderList count];
  616.     for ( i=0; i<count; i++ )
  617.         if ( !strcmp ( path, [[folderList objectAt:i] filename] ) )
  618.             return ( [folderList objectAt:i] );
  619.             
  620.     return ( nil );
  621. }
  622.  
  623.  
  624.  
  625. - folderWithViewerNum:(int)winNum
  626. /*
  627.     Finds the folder whose Viewer has the global window number winNum
  628. */
  629. {
  630.     int i, count;
  631.     
  632.     if ( winNum == appIconNum ) { // winNum is the AppIcon window
  633.         return ( keyFolder );
  634.     }
  635.     
  636.     count=[folderList count];
  637.     for ( i=0 ; i<count; i++ )
  638.         if ( [[folderList objectAt:i] viewerNum] == winNum )
  639.             return ( [folderList objectAt:i] );
  640.  
  641.     return ( nil ); // Didn't find a match
  642. }
  643.  
  644.  
  645.  
  646. - (BOOL)folderLoaded:(const char *)path
  647. /*
  648.     Returns YES if the Folder specified by path is already opened.
  649. */
  650. {
  651.     return ( [self folderWithFilename:path] ? YES : NO );
  652. }
  653.  
  654.  
  655.  
  656. // -------------------------------------------------------------------------
  657. //   Groups
  658. // -------------------------------------------------------------------------
  659.  
  660.  
  661. - newGroup:sender
  662. {
  663.     // Forward to keyFolder
  664.     [keyFolder newGroup:sender] ;
  665.     return ( self );
  666. }
  667.  
  668.  
  669.  
  670. - deleteCurrentGroup:sender
  671. {
  672.     // Forward message to keyFolder
  673.     [keyFolder deleteCurrentGroup:sender];
  674.     return ( self );
  675. }
  676.  
  677.  
  678.  
  679. - launchCurrentGroup:sender
  680. {
  681.     // Forward message to keyFolder
  682.     [keyFolder launchCurrentGroup:sender];
  683.     [self perform:@selector(updateDisplay:) with:self
  684.             afterDelay:100 cancelPrevious:NO];
  685.     return ( self );
  686. }
  687.  
  688.  
  689.  
  690. - explicitSortItems:sender
  691. {
  692.     // Forward message to keyFolder
  693.     [[keyFolder currentGroup] explicitSortItems:sender];
  694.     [self updateDisplay];
  695.     return ( self );
  696. }
  697.  
  698.  
  699.  
  700. - cleanUpCurrentGroup:sender
  701. {
  702.     // Forward message to keyFolder
  703.     [[keyFolder currentGroup] cleanUp:sender];
  704.     [self updateDisplay];
  705.     return ( self );
  706. }
  707.  
  708.  
  709.  
  710. - dynamicUpdateCurrentGroup:sender
  711. {
  712.     // Forward message to keyFolder
  713.     [[keyFolder currentGroup] updateDynamicItems:sender];
  714.     [self updateDisplay];
  715.     return ( self );
  716. }
  717.  
  718.  
  719.  
  720. // -------------------------------------------------------------------------
  721. //   Items
  722. // -------------------------------------------------------------------------
  723.  
  724.  
  725. - runAddPanel:sender
  726. /*
  727.     Use an OpenPanel to choose items to be added to keyFolder's currentGroup
  728. */
  729. {
  730.     id addPanel = [OpenPanel new];
  731.     const char *const *files;
  732.     char path[MAXPATHLEN+1];
  733.     id group = [keyFolder currentGroup];
  734.     int i;
  735.     
  736.     [[addPanel setTitle:"Add Items"] setPrompt:"Path:"];
  737.     [addPanel allowMultipleFiles:YES];
  738.     [addPanel chooseDirectories:NO];
  739.     
  740.     if ( [addPanel runModalForDirectory:[group defaultPath]
  741.             file:NULL types:([group doesRestrictTypes]
  742.                 ? [group allowedTypes] : NULL)] ) {
  743.         
  744.         // Add selected items
  745.         i = 0;
  746.         files = [addPanel filenames];
  747.         while ( files[i] ) {
  748.             sprintf ( path, "%s/%s", [addPanel directory], files[i] );
  749.             [[keyFolder currentGroup] addItem:path];
  750.             i++;
  751.         }
  752.         
  753.         [[keyFolder currentGroup] sortItems];
  754.     }
  755.     
  756.     [self updateDisplay];
  757.     return ( self );
  758. }    
  759.  
  760.  
  761.  
  762. - launchItems:sender
  763. {
  764.     // Forward message to keyFolder
  765.     [[keyFolder currentGroup] launchSelectedItems:sender];
  766.     return ( self );
  767. }
  768.  
  769.  
  770.  
  771. - removeItems:sender
  772. {
  773.     int ret;
  774.     
  775.     // Prompt user to verify that the items should be removed
  776.     if ( verifyActions ) {
  777.         if ( [[keyFolder currentGroup] selectionCount] == 1)
  778.             ret = NXRunAlertPanel ( "Verify", "Are you sure you want to remove the item %s from the group %s ?",
  779.                 "Remove", "Cancel", NULL,
  780.                 [[[keyFolder currentGroup] selection] filename],
  781.                 [[keyFolder currentGroup] groupName] );
  782.         else
  783.             ret = NXRunAlertPanel ( "Verify", "Are you sure you want to remove %i selected items from the group %s ?",
  784.                 "Remove", "Cancel", NULL,
  785.                 [[keyFolder currentGroup] selectionCount],
  786.                 [[keyFolder currentGroup] groupName] );
  787.     } else
  788.         ret = NX_ALERTDEFAULT;
  789.     
  790.     switch ( ret ) {
  791.         case NX_ALERTDEFAULT: // Remove
  792.             // Forward message to keyFolder
  793.             [[keyFolder currentGroup] removeSelectedItems:self];
  794.             [self updateDisplay];
  795.             break;
  796.         case NX_ALERTALTERNATE: // Cancel
  797.             break;
  798.     }
  799.     
  800.     return ( self );
  801. }
  802.  
  803.  
  804.  
  805. // -------------------------------------------------------------------------
  806. //   Text Editing methods
  807. // -------------------------------------------------------------------------
  808.  
  809.  
  810. - cut:sender
  811. /*
  812.     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.
  813. */
  814. {
  815.     id keyWindow = [NXApp keyWindow];
  816.     
  817.     // The keyWindow is the keyFolder's viewer
  818.     if ( keyWindow == [keyFolder viewer] ) {
  819.         [self copy:self]; // Copy the items to the pasteboard
  820.         [self removeItems:self]; // Delete the items
  821.     } else
  822.         [[keyWindow firstResponder] tryToPerform:@selector(cut:) with:self];
  823.     
  824.     return ( self );
  825. }
  826.  
  827.  
  828.  
  829. - copy:sender
  830. /*
  831.     If the keyWindow is the keyFolder's viewer, the selected items are copied to itemPboard.  Otherwise, this message is forwarded to the firstResponder.
  832. */
  833. {
  834.     id keyWindow = [NXApp keyWindow];
  835.     const char *const pboardTypes[2] = { NXFilenamePboardType, NULL };
  836.     NXStream *stream;
  837.     char *body;
  838.     int len, mlen;
  839.     int i, count, k;
  840.     id list;
  841.     
  842.     if ( keyWindow == [keyFolder viewer]  ) { // Write selected items to pboard
  843.         list = [[keyFolder currentGroup] selectionList];
  844.         stream = NXOpenMemory ( NULL, 0, NX_WRITEONLY );
  845.         if ( !stream ) { // Couldn't open stream
  846.             [self errMsg:"copy: Unable to copy Items to Pboard\n"];
  847.             [list free];
  848.             return ( nil );
  849.         }
  850.         
  851.         // Write selected paths to stream as tab-separated list
  852.         for ( i=0, count=[list count], k=0; i<count; i++ ) {
  853.             if ( [[list objectAt:i] state] ) {
  854.                 if ( k>0 ) NXPutc ( stream, '\t' );
  855.                 NXPrintf ( stream, "%s", [[list objectAt:i] path] );
  856.                 k++;
  857.             }
  858.         }
  859.         
  860.         // Write list to itemPboard
  861.         NXGetMemoryBuffer ( stream, &body, &len, &mlen );
  862.         [itemPboard declareTypes:pboardTypes num:1 owner:nil];
  863.         [itemPboard writeType:NXFilenamePboardType data:body length:len];
  864.  
  865.         NXCloseMemory ( stream, NX_FREEBUFFER );
  866.         [list free];    // We are responsible to free this
  867.     } else
  868.         [[keyWindow firstResponder] tryToPerform:@selector(copy:) with:self];
  869.     
  870.     return ( self );
  871. }
  872.  
  873.  
  874.  
  875. - paste:sender
  876. /*
  877.     If keyWindow is the keyFolder's viewer, all items on the itemPboard are added to the currentGroup.  Otherwise this message is forwarded to firstResponder
  878. */
  879. {
  880.     id keyWindow = [NXApp keyWindow];
  881.     const char * const *types;
  882.     char *list = NULL;
  883.     int len, i=0;
  884.     char *s, *tab, path[MAXPATHLEN+1];
  885.     
  886.     if ( keyWindow == [keyFolder viewer] ) {
  887.         
  888.         if ( ![keyFolder currentGroup] ) {
  889.             NXBeep();
  890.             return ( nil );
  891.         }
  892.             
  893.         // Get list of items from itemPboard
  894.         types = [itemPboard types];
  895.         while ( types[i] ) {
  896.             if ( !strcmp ( types[i], NXFilenamePboardType ) ) {
  897.                 if ( ![itemPboard readType:NXFilenamePboardType
  898.                         data:&list length:&len] ) {
  899.                     list = NULL;
  900.                     len = 0;
  901.                 }
  902.                 break;
  903.             }
  904.             i++;
  905.         }
  906.         
  907.         // Add all the items in the list
  908.         if ( list ) {
  909.             s = list;
  910.             do {
  911.                 // Read the next path, at s
  912.                 sscanf ( s, "%[^\t]", path );
  913.                 
  914.                 // Add the path
  915.                 [[keyFolder currentGroup] addItem:path];
  916.                 
  917.                 // Move s to next path
  918.                 tab = index ( s, '\t' ); // Find next tab
  919.                 if ( tab ) s = tab + 1;
  920.                     else s = NULL;
  921.             } while ( s );
  922.         
  923.             // Free the memory used by list
  924.             vm_deallocate ( task_self(), (vm_address_t)list, len );
  925.                 
  926.         }
  927.         
  928.         [[keyFolder currentGroup] sortItems];
  929.         [self updateDisplay];
  930.     } else
  931.         [[keyWindow firstResponder] tryToPerform:@selector(paste:) with:self];
  932.     
  933.     return ( self );
  934. }
  935.  
  936.  
  937.  
  938. - selectAll:sender
  939. /*
  940.     If keyWindow is the keyFolder's viewer, select all items in the keyFolder.  Otherwise, forward this message to firstResponder.
  941. */
  942. {
  943.     id keyWindow = [NXApp keyWindow];
  944.     
  945.     if ( keyWindow == [keyFolder viewer] ) {
  946.         if ( ![[keyFolder currentGroup] selectAll:sender] ) NXBeep();
  947.     } else
  948.         [[keyWindow firstResponder]
  949.                 tryToPerform:@selector(selectAll:) with:self];
  950.     
  951.     return ( self );
  952. }
  953.  
  954.  
  955.  
  956. // -------------------------------------------------------------------------
  957. //   Tools
  958. // -------------------------------------------------------------------------
  959.  
  960.  
  961. - showInspector:sender
  962. {
  963.     [inspector showInspector:self];
  964.     return ( self );
  965. }
  966.  
  967.  
  968.  
  969. - toggleHideOnDeactivate:sender
  970. /*
  971.     Toggle the state of the hideDeactivate global variable.  This state doesn't get saved unless writePrefs:(MainController) gets called at some point
  972. */
  973. {
  974.     //// FIX: make sure this gets done whenever hideDeactive changes
  975.     //// (in Prefs, etc)
  976.     
  977.     int i, count;
  978.     hideDeactive = hideDeactive ? NO : YES;
  979.     count = [folderList count];
  980.     for ( i=0; i<count; i++ ) {
  981.         [[[folderList objectAt:i] viewer] setHideOnDeactivate:hideDeactive];
  982.     }
  983.     [self updateMenu:self];
  984.     return ( self );
  985. }
  986.  
  987.  
  988.  
  989. // -------------------------------------------------------------------------
  990. //   DragDelegate Methods -- forwarded from the dragDest view/window
  991. // -------------------------------------------------------------------------
  992.  
  993.  
  994. static BOOL wasActive = NO;
  995.  
  996.  
  997. - setDragDest:object
  998. {
  999.     dragDest = object;
  1000.     return ( self );
  1001. }
  1002.  
  1003.  
  1004.  
  1005. - dragDest
  1006. {
  1007.     return ( dragDest );
  1008. }
  1009.  
  1010.  
  1011.  
  1012. #define DRAG_OP    (NX_DragOperationAll)
  1013. - (NXDragOperation)draggingEntered:(id <NXDraggingInfo>)sender
  1014. /*
  1015.     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.
  1016. */
  1017. {
  1018.     if ( [keyFolder currentGroup] && dragDest == [keyFolder currentGroup] ) {
  1019.         // Don't allowing dragging into group we are dragging out of
  1020.         return ( NX_DragOperationNone );
  1021.     }
  1022.     
  1023.     wasActive = [NXApp isActive];
  1024.     [appIconView setIcon:"AcceptIcon"];    
  1025.     return ( [sender draggingSourceOperationMask] & DRAG_OP );
  1026. }
  1027.  
  1028.  
  1029.  
  1030. - (NXDragOperation)draggingUpdated:(id <NXDraggingInfo>)sender
  1031. {
  1032.     return ( DRAG_OP );
  1033. }
  1034.  
  1035.  
  1036.  
  1037. - draggingExited:(id <NXDraggingInfo>)sender
  1038. {
  1039.     [self setDragDest:nil];
  1040.     [appIconView setIcon:( wasActive ? "ActiveIcon" : "InactiveIcon")];
  1041.     return ( nil );
  1042. }
  1043.  
  1044.  
  1045.  
  1046. - (BOOL)prepareForDragOperation:(id <NXDraggingInfo>)sender
  1047. {
  1048.     return ( YES );
  1049. }
  1050.  
  1051.  
  1052.         
  1053. - (BOOL)performDragOperation:(id <NXDraggingInfo>)sender
  1054. /*
  1055.     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. 
  1056. */
  1057. {
  1058.     Folder    *destFolder = nil;
  1059.     char *paths;
  1060.     int pLen;
  1061.     char *p, path[MAXPATHLEN+1];
  1062.     
  1063.     // Determine the folder being dragged into
  1064.     if ( dragDest == appIconView || dragDest == activator )
  1065.         destFolder = keyFolder;
  1066.     else if ( [dragDest isKindOf:[GroupBrowser class]] )
  1067.         destFolder = [[dragDest window] delegate];
  1068.     if ( ![destFolder currentGroup] ) {
  1069.         NXRunAlertPanel ( "Error",
  1070.             "No destination folder and/or group to drag into!",
  1071.             NULL, NULL, NULL );
  1072.         vm_deallocate ( task_self(), (vm_address_t)paths, pLen );
  1073.         return ( YES );
  1074.     }
  1075.     
  1076.     // Parse path list, add the files
  1077.     // the path list is in the NXFilenamePboardType format, which
  1078.     // means that it is a list of tab-separated paths.
  1079.     [[sender draggingPasteboard] readType:NXFilenamePboardType
  1080.         data:&paths length:&pLen];
  1081.     
  1082.     // Read all the paths in the list, add them to the currentGroup
  1083.     p = paths;
  1084.     while ( p ) {
  1085.         sscanf ( p, "%[^\t]", path );
  1086.         [[destFolder currentGroup] addItem:path];
  1087.         if ( (p = index ( p,'\t' )) ) p++;
  1088.     }
  1089.     
  1090.     // Free the path list -- our responsibility to do this
  1091.     vm_deallocate ( task_self(), (vm_address_t)paths, pLen );
  1092.     
  1093.     [[destFolder currentGroup] sortItems];
  1094.  
  1095.     // Send the showSelf: delayed to avoid loopback problems...
  1096.     [destFolder perform:@selector(showSelf:) with:self
  1097.         afterDelay:1 cancelPrevious:NO];
  1098.     
  1099.     return ( YES );
  1100. }
  1101.  
  1102.  
  1103.     
  1104. - concludeDragOperation:(id <NXDraggingInfo>)sender
  1105. {
  1106.     [self setDragDest:nil];
  1107.     [appIconView setIcon:( wasActive ? "ActiveIcon" : "InactiveIcon")];
  1108.     return ( self );
  1109. }
  1110.  
  1111.  
  1112.     
  1113. // -------------------------------------------------------------------------
  1114. //   Misc
  1115. // -------------------------------------------------------------------------
  1116.  
  1117.  
  1118. - updateMenu:sender
  1119. /*
  1120.     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.
  1121. */ 
  1122. {
  1123.     if ( !keyFolder ) {
  1124.         [menu_SaveFolder setEnabled:NO];
  1125.         [menu_SaveFolderAs setEnabled:NO];
  1126.         [menu_SaveAll setEnabled:NO];
  1127.         [menu_RevertFolder setEnabled:NO];
  1128.         [menu_NewGroup setEnabled:NO];
  1129.         [menu_DeleteGroup setEnabled:NO];
  1130.         [menu_LaunchGroup setEnabled:NO];
  1131.         [menu_SortGroup setEnabled:NO];
  1132.         [menu_CleanGroup setEnabled:NO];
  1133.         [menu_DynamicUpdate setEnabled:NO];
  1134.         [menu_AddItem setEnabled:NO];
  1135.         [menu_LaunchItem setEnabled:NO];
  1136.         [menu_DeleteItem setEnabled:NO];
  1137.     } else {
  1138.         [menu_SaveFolder setEnabled:YES];
  1139.         [menu_SaveFolderAs setEnabled:YES];
  1140.         [menu_SaveAll setEnabled:YES];
  1141.         [menu_RevertFolder setEnabled:YES];
  1142.         [menu_NewGroup setEnabled:YES];
  1143.         
  1144.         if ( [keyFolder currentGroup] ) {
  1145.             [menu_DeleteGroup setEnabled:YES];
  1146.             [menu_LaunchGroup setEnabled:YES];
  1147.             [menu_SortGroup setEnabled:YES];
  1148.             [menu_CleanGroup setEnabled:YES];
  1149.             [menu_DynamicUpdate setEnabled:YES];
  1150.             [menu_AddItem setEnabled:YES];
  1151.             [menu_LaunchItem setEnabled:YES];
  1152.             [menu_DeleteItem setEnabled:YES];
  1153.         } else {
  1154.             [menu_DeleteGroup setEnabled:NO];
  1155.             [menu_LaunchGroup setEnabled:NO];
  1156.             [menu_SortGroup setEnabled:NO];
  1157.             [menu_CleanGroup setEnabled:NO];
  1158.             [menu_DynamicUpdate setEnabled:NO];
  1159.             [menu_AddItem setEnabled:NO];
  1160.             [menu_LaunchItem setEnabled:NO];
  1161.             [menu_DeleteItem setEnabled:NO];
  1162.         }
  1163.     }
  1164.     
  1165.     [menu_HideOnDeactivate setTitle:
  1166.         (hideDeactive ? "Don't hide" : "Hide on deactivate")];
  1167.  
  1168.     return ( self );
  1169. }
  1170.  
  1171.  
  1172.  
  1173. - updateDisplay
  1174. /*
  1175.     Update the display of the keyFolder, and the inspector.
  1176. */
  1177. {
  1178.     [keyFolder showSelf:self];
  1179.     [inspector update];
  1180.     return ( self );
  1181. }
  1182.  
  1183.  
  1184.  
  1185. - updateDisplay:sender
  1186. /*
  1187.     Just calls -updateDisplay.  This is basically used so updateDisplay can be used with a delayed perform ( perform:with:afterDelay:cancelPrevious: )
  1188. */
  1189. {
  1190.     return ( [self updateDisplay] );
  1191. }
  1192.  
  1193.  
  1194.  
  1195. @end
  1196.  
  1197.  
  1198.