Fix Populated NSMenu Of Files, Folders And Their Respected Images

  • hi.

    i've been on this task for almost a week, and i still can't get it
    right.  basically, i would like to have an NSMenu populated with all
    the files and folders of a specific directory.  for this example i'll
    use ~/Documents.  the code below only partially works.  it will list
    files and folders from Documents in an NSMenu, but they are not
    selectable.  i've added a @selector, but it doesn't seem to be called.
    the selector if just an example method - i don't want to really
    terminate the app when an item is selected, i simply want the item to
    launch.

    second, it would be ideal to have the folders also produce their own
    NSMenu list of files contained in them - i have no idea how to do
    this.

    third, the code below codes the NSMenuItem to be titled "Documents",
    but it's title is not displayed when it's inserted at index:1 of
    NSMenu *theMenu.  however, if i insert it into [NSApp mainMenu], the
    title "Documents" will display.  this doesn't make sense to me.

    and finally, i would love to have each of the files and folders to be
    added to the NSMenu with their respected icons.  my attempts to do
    this have failed because i do not know how to get the path of each
    file or folder that is being enumerated.  the code below will warn
    that NSString does not respond to Path.

    to me this whole thing seems like something very common, and would be
    used a lot by developers, but i've had such difficulty trying to find
    resources.  i've found bits and pieces and lots of dead ends.  please
    someone help me with this.  sample code would be amazing... AMAZING!
    or at least a guide into the right direction.

    thanks in advance :)

    -=-=-=- CODE -=-=-=-

    - (void)awakeFromNib
    {
    NSMenu *documentsMenu = [[NSMenu alloc] initWithTitle:@"Documents"];
    NSMenuItem *documentsItem = [[NSMenuItem alloc] initWithTitle: @""
    action: nil keyEquivalent: @""];
    [documentsItem setSubmenu:documentsMenu];

    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSString *documentsDirectory = [@"~/Desktop" stringByExpandingTildeInPath];
    NSDirectoryEnumerator *directoryEnumerator = [fileManager
    enumeratorAtPath:documentsDirectory];

    NSString *fileOrFolder;
    while ((fileOrFolder = [directoryEnumerator nextObject]))
      {
      //ignore invisible files
      if ([fileOrFolder hasPrefix:@"."])
      {
      continue;
      }

      NSLog(fileOrFolder);

      NSMenuItem *documentsItem = [[NSMenuItem alloc]
    initWithTitle:fileOrFolder action:@selector(launchFileOrFolder:)
    keyEquivalent:@""];

      NSDictionary *fileAttributes = [fileManager
    fileAttributesAtPath:documentsDirectory traverseLink:NO];
      if ([[fileAttributes fileType] isEqualToString:NSFileTypeDirectory])
      {
      [directoryEnumerator skipDescendents];
      }

      [documentsItem setImage:[[NSWorkspace sharedWorkspace]
    iconForFile:[documentsItem path]]];
      [documentsMenu addItem:documentsItem];
      [documentsItem release];
      }

    //"theMenu" is an IBOutlet to an NSMenu with 2 items already listed.
    i place documentsItem between those 2 items at index:1.
    [theMenu insertItem:documentsItem atIndex:1];
    //[[NSApp mainMenu] insertItem:documentsItem atIndex:1];
    [documentsMenu release];
    [documentsItem release];
    }

    - (void)launchFileOrFolder
    {
    [NSApp terminate];
    }

    -=-=-=- END CODE -=-=-=-
  • On Dec 5, 2008, at 9:44 AM, Chunk 1978 wrote:

    > hi.

    Hi.

    > i've been on this task for almost a week, and i still can't get it
    > right.  basically, i would like to have an NSMenu populated with all
    > the files and folders of a specific directory.  for this example i'll
    > use ~/Documents.  the code below only partially works.  it will list
    > files and folders from Documents in an NSMenu, but they are not
    > selectable.  i've added a @selector, but it doesn't seem to be called.
    > the selector if just an example method - i don't want to really
    > terminate the app when an item is selected, i simply want the item to
    > launch.

    You have to also call setTarget: on the menu items so it knows which
    object to send the message to. In your case you'd probably pass self
    as the object. Also if you want to access the path again during your
    selector it would probably be easiest to set the path for the item as
    the representedObject.

    NSMenuItem *item = /* your item with @selector */
    [item setTarget:self];
    [item setRepresentedObject:[documentsDirectory
    stringByAppendingPathComponent:fileOrFolder]];

    Then in your method:

    - (void)actionMethod:(id)sender {
    id fullPath = [sender representedObject];

    /* do something with fullPath */
    }

    > second, it would be ideal to have the folders also produce their own
    > NSMenu list of files contained in them - i have no idea how to do
    > this.

    You'll have to do some refactoring to accomplish this. Essentially,
    break apart your method into one that creates a menu for a directory's
    items and returns the resulting menu. That method will call itself to
    create menus for subfolders which you would add as subitems to your
    menu. You would then just call that method on your root directory,
    Documents in your case, and add the resulting tree of menus wherever
    you're interested in.

    > third, the code below codes the NSMenuItem to be titled "Documents",
    > but it's title is not displayed when it's inserted at index:1 of
    > NSMenu *theMenu.  however, if i insert it into [NSApp mainMenu], the
    > title "Documents" will display.  this doesn't make sense to me.

    I believe this is a consequence of how you're setting the title and
    where you're adding the menu. If you set the title on the
    documentsItem as well as the documentsMenu it should appear regardless
    of where you attach the menu.

    > and finally, i would love to have each of the files and folders to be
    > added to the NSMenu with their respected icons.  my attempts to do
    > this have failed because i do not know how to get the path of each
    > file or folder that is being enumerated.  the code below will warn
    > that NSString does not respond to Path.

    You can get the icon for the path with this:

    [[NSWorkspace sharedWorkspace] iconForFile:[documentsDirectory
    stringByAppendingPathComponent:fileOrFolder]];

    Ashley
  • thanks ashley, you cleared up a lot for me. however, i'm now presented
    with two (seemingly) small issues.  the first is determining whether
    the directory is empty.  i've added an array with
    directoryContentsAtPath, and an if statement counting the array, but
    it doesn't seem to work.

    second, how is it possible to change the size of the iconForFile(s)
    using setSize:NSMakeSize(16, 16), or any other way?

    my updated code follows:

    -=-=-=-=-

    - (void)awakeFromNib
    {
    NSMenu *desktopMenu = [[NSMenu alloc] initWithTitle:@""];
    NSMenuItem *desktopItem = [[NSMenuItem alloc] initWithTitle:@"Desktop
    Files" action:nil keyEquivalent:@""];
    [desktopItem setSubmenu:desktopMenu];

    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSString *desktopDirectory = [@"~/Desktop/9" stringByExpandingTildeInPath];
    NSDirectoryEnumerator *directoryEnumerator = [fileManager
    enumeratorAtPath:desktopDirectory];
    NSString *fileOrFolder;
    NSArray *theFolderContents = [fileManager
    directoryContentsAtPath:desktopDirectory];

    if ([theFolderContents count] == 0)
      {
      NSLog(@"no files");
      NSMenuItem *noFilesItem = [[NSMenuItem alloc] initWithTitle:@"No
    Files" action:nil keyEquivalent:@""];
      [desktopMenu addItem:noFilesItem];
      [noFilesItem release];
      }
      else
      {
      while ((fileOrFolder = [directoryEnumerator nextObject]))
      {
      //ignore invisible files
      if ([fileOrFolder hasPrefix:@"."])
        {
        continue;
        }

      [directoryEnumerator skipDescendents];

      NSMenuItem *desktopItems = [[NSMenuItem alloc]
    initWithTitle:fileOrFolder action:@selector(launchFileOrFolder:)
    keyEquivalent:@""];
      [desktopItems setTarget:self];
      [desktopItems setRepresentedObject:[desktopDirectory
    stringByAppendingPathComponent:fileOrFolder]];

      [desktopItems setImage:[[NSWorkspace sharedWorkspace]
    iconForFile:[desktopDirectory
    stringByAppendingPathComponent:fileOrFolder]]];

      [desktopMenu addItem:desktopItems];
      [desktopItems release];
      }
      }

    [theMenu insertItem:desktopItem atIndex:1];
    [desktopMenu release];
    [desktopItem release];
    }

    - (void)launchFileOrFolder:(id)sender
    {
    NSString *filePath = [sender representedObject];
    [[NSWorkspace sharedWorkspace] openFile:filePath];
    }

    - (void)dealloc
    {
    [super dealloc];
    }

    -=-=-=-=-

    On Fri, Dec 5, 2008 at 3:07 PM, Ashley Clark <aclark...> wrote:
    > On Dec 5, 2008, at 9:44 AM, Chunk 1978 wrote:
    >
    >> hi.
    >
    > Hi.
    >
    >> i've been on this task for almost a week, and i still can't get it
    >> right.  basically, i would like to have an NSMenu populated with all
    >> the files and folders of a specific directory.  for this example i'll
    >> use ~/Documents.  the code below only partially works.  it will list
    >> files and folders from Documents in an NSMenu, but they are not
    >> selectable.  i've added a @selector, but it doesn't seem to be called.
    >> the selector if just an example method - i don't want to really
    >> terminate the app when an item is selected, i simply want the item to
    >> launch.
    >
    > You have to also call setTarget: on the menu items so it knows which object
    > to send the message to. In your case you'd probably pass self as the object.
    > Also if you want to access the path again during your selector it would
    > probably be easiest to set the path for the item as the representedObject.
    >
    > NSMenuItem *item = /* your item with @selector */
    > [item setTarget:self];
    > [item setRepresentedObject:[documentsDirectory
    > stringByAppendingPathComponent:fileOrFolder]];
    >
    > Then in your method:
    >
    > - (void)actionMethod:(id)sender {
    > id fullPath = [sender representedObject];
    >
    > /* do something with fullPath */
    > }
    >
    >> second, it would be ideal to have the folders also produce their own
    >> NSMenu list of files contained in them - i have no idea how to do
    >> this.
    >
    > You'll have to do some refactoring to accomplish this. Essentially, break
    > apart your method into one that creates a menu for a directory's items and
    > returns the resulting menu. That method will call itself to create menus for
    > subfolders which you would add as subitems to your menu. You would then just
    > call that method on your root directory, Documents in your case, and add the
    > resulting tree of menus wherever you're interested in.
    >
    >> third, the code below codes the NSMenuItem to be titled "Documents",
    >> but it's title is not displayed when it's inserted at index:1 of
    >> NSMenu *theMenu.  however, if i insert it into [NSApp mainMenu], the
    >> title "Documents" will display.  this doesn't make sense to me.
    >
    > I believe this is a consequence of how you're setting the title and where
    > you're adding the menu. If you set the title on the documentsItem as well as
    > the documentsMenu it should appear regardless of where you attach the menu.
    >
    >> and finally, i would love to have each of the files and folders to be
    >> added to the NSMenu with their respected icons.  my attempts to do
    >> this have failed because i do not know how to get the path of each
    >> file or folder that is being enumerated.  the code below will warn
    >> that NSString does not respond to Path.
    >
    > You can get the icon for the path with this:
    >
    > [[NSWorkspace sharedWorkspace] iconForFile:[documentsDirectory
    > stringByAppendingPathComponent:fileOrFolder]];
    >
    >
    > Ashley
    >
    >
  • nevermind, i managed to get it right... not sure how efficient this
    code is, but at least it works :)

    -=-=-=-=-

    @interface AppController : NSObject
    {
    IBOutlet NSMenu *theMenu;
    }

    - (void)launchFileOrFolder:(id)sender;

    @end

    -=-=-=-=-=-

    #import "AppController.h"

    @implementation AppController

    - (void)awakeFromNib
    {
    NSMenu *desktopMenu = [[NSMenu alloc] initWithTitle:@""];
    NSMenuItem *desktopItem = [[NSMenuItem alloc] initWithTitle:@"Desktop
    Files" action:nil keyEquivalent:@""];
    [desktopItem setSubmenu:desktopMenu];

    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSString *desktopDirectory = [@"~/Desktop" stringByExpandingTildeInPath];
    NSDirectoryEnumerator *directoryCount = [fileManager
    enumeratorAtPath:desktopDirectory];
    NSDirectoryEnumerator *directoryEnumerator = [fileManager
    enumeratorAtPath:desktopDirectory];
    NSString *fileOrFolder;

    int hiddenFIles = 0;
    while ((fileOrFolder = [directoryCount nextObject]))
      {
      if ([fileOrFolder hasPrefix:@"."])
      {
      hiddenFIles++;
      }
      }
    NSLog(@"hiddenfiles = %d", hiddenFIles);

    NSArray *directoryContents = [fileManager
    directoryContentsAtPath:desktopDirectory];
    int allFiles = [directoryContents count];
    NSLog(@"totalfiles = %d", allFiles);


    if ((allFiles - hiddenFIles) == 0)
      {
      NSLog(@"directory contains only hidden files");
      NSMenuItem *noFilesItem = [[NSMenuItem alloc] initWithTitle:@"Empty"
    action:nil keyEquivalent:@""];
      [desktopMenu addItem:noFilesItem];
      [noFilesItem release];

      //or alternatively just disable the item
      //with this line of code:  [desktopItem setEnabled:NO];

      }
      else
      {
      while ((fileOrFolder = [directoryEnumerator nextObject]))
      {
      //ignore invisible files
      if ([fileOrFolder hasPrefix:@"."])
        {
        continue;
        }

      [directoryEnumerator skipDescendents];

      NSMenuItem *desktopItems = [[NSMenuItem alloc]
    initWithTitle:fileOrFolder action:@selector(launchFileOrFolder:)
    keyEquivalent:@""];
      [desktopItems setTarget:self];
      [desktopItems setRepresentedObject:[desktopDirectory
    stringByAppendingPathComponent:fileOrFolder]];

      NSImage *fileOrFolderIcons = [[NSWorkspace sharedWorkspace]
    iconForFile:[desktopDirectory
    stringByAppendingPathComponent:fileOrFolder]];
      [fileOrFolderIcons setScalesWhenResized:YES];
      NSSize mySize; mySize.width=16; mySize.height=16;
      [fileOrFolderIcons setSize:mySize];
      [desktopItems setImage:fileOrFolderIcons];

      [desktopMenu addItem:desktopItems];
      [desktopItems release];
      }
      }
    [theMenu insertItem:desktopItem atIndex:1];
    [desktopMenu release];
    [desktopItem release];
    }

    - (void)launchFileOrFolder:(id)sender
    {
    NSString *filePath = [sender representedObject];
    [[NSWorkspace sharedWorkspace] openFile:filePath];
    }

    - (void)dealloc
    {
    [super dealloc];
    }

    @end

    -=-=-=-=-

    On Sat, Dec 6, 2008 at 6:01 AM, Chunk 1978 <chunk1978...> wrote:
    > thanks ashley, you cleared up a lot for me. however, i'm now presented
    > with two (seemingly) small issues.  the first is determining whether
    > the directory is empty.  i've added an array with
    > directoryContentsAtPath, and an if statement counting the array, but
    > it doesn't seem to work.
    >
    > second, how is it possible to change the size of the iconForFile(s)
    > using setSize:NSMakeSize(16, 16), or any other way?
    >
    > my updated code follows:
    >
    > -=-=-=-=-
    >
    > - (void)awakeFromNib
    > {
    > NSMenu *desktopMenu = [[NSMenu alloc] initWithTitle:@""];
    > NSMenuItem *desktopItem = [[NSMenuItem alloc] initWithTitle:@"Desktop
    > Files" action:nil keyEquivalent:@""];
    > [desktopItem setSubmenu:desktopMenu];
    >
    > NSFileManager *fileManager = [NSFileManager defaultManager];
    > NSString *desktopDirectory = [@"~/Desktop/9" stringByExpandingTildeInPath];
    > NSDirectoryEnumerator *directoryEnumerator = [fileManager
    > enumeratorAtPath:desktopDirectory];
    > NSString *fileOrFolder;
    > NSArray *theFolderContents = [fileManager
    > directoryContentsAtPath:desktopDirectory];
    >
    > if ([theFolderContents count] == 0)
    > {
    > NSLog(@"no files");
    > NSMenuItem *noFilesItem = [[NSMenuItem alloc] initWithTitle:@"No
    > Files" action:nil keyEquivalent:@""];
    > [desktopMenu addItem:noFilesItem];
    > [noFilesItem release];
    > }
    > else
    > {
    > while ((fileOrFolder = [directoryEnumerator nextObject]))
    > {
    > //ignore invisible files
    > if ([fileOrFolder hasPrefix:@"."])
    > {
    > continue;
    > }
    >
    > [directoryEnumerator skipDescendents];
    >
    > NSMenuItem *desktopItems = [[NSMenuItem alloc]
    > initWithTitle:fileOrFolder action:@selector(launchFileOrFolder:)
    > keyEquivalent:@""];
    > [desktopItems setTarget:self];
    > [desktopItems setRepresentedObject:[desktopDirectory
    > stringByAppendingPathComponent:fileOrFolder]];
    >
    > [desktopItems setImage:[[NSWorkspace sharedWorkspace]
    > iconForFile:[desktopDirectory
    > stringByAppendingPathComponent:fileOrFolder]]];
    >
    > [desktopMenu addItem:desktopItems];
    > [desktopItems release];
    > }
    > }
    >
    > [theMenu insertItem:desktopItem atIndex:1];
    > [desktopMenu release];
    > [desktopItem release];
    > }
    >
    > - (void)launchFileOrFolder:(id)sender
    > {
    > NSString *filePath = [sender representedObject];
    > [[NSWorkspace sharedWorkspace] openFile:filePath];
    > }
    >
    > - (void)dealloc
    > {
    > [super dealloc];
    > }
    >
    > -=-=-=-=-
    >
    >
    >
    >
    >
    >
    >
    >
    >
    > On Fri, Dec 5, 2008 at 3:07 PM, Ashley Clark <aclark...> wrote:
    >> On Dec 5, 2008, at 9:44 AM, Chunk 1978 wrote:
    >>
    >>> hi.
    >>
    >> Hi.
    >>
    >>> i've been on this task for almost a week, and i still can't get it
    >>> right.  basically, i would like to have an NSMenu populated with all
    >>> the files and folders of a specific directory.  for this example i'll
    >>> use ~/Documents.  the code below only partially works.  it will list
    >>> files and folders from Documents in an NSMenu, but they are not
    >>> selectable.  i've added a @selector, but it doesn't seem to be called.
    >>> the selector if just an example method - i don't want to really
    >>> terminate the app when an item is selected, i simply want the item to
    >>> launch.
    >>
    >> You have to also call setTarget: on the menu items so it knows which object
    >> to send the message to. In your case you'd probably pass self as the object.
    >> Also if you want to access the path again during your selector it would
    >> probably be easiest to set the path for the item as the representedObject.
    >>
    >> NSMenuItem *item = /* your item with @selector */
    >> [item setTarget:self];
    >> [item setRepresentedObject:[documentsDirectory
    >> stringByAppendingPathComponent:fileOrFolder]];
    >>
    >> Then in your method:
    >>
    >> - (void)actionMethod:(id)sender {
    >> id fullPath = [sender representedObject];
    >>
    >> /* do something with fullPath */
    >> }
    >>
    >>> second, it would be ideal to have the folders also produce their own
    >>> NSMenu list of files contained in them - i have no idea how to do
    >>> this.
    >>
    >> You'll have to do some refactoring to accomplish this. Essentially, break
    >> apart your method into one that creates a menu for a directory's items and
    >> returns the resulting menu. That method will call itself to create menus for
    >> subfolders which you would add as subitems to your menu. You would then just
    >> call that method on your root directory, Documents in your case, and add the
    >> resulting tree of menus wherever you're interested in.
    >>
    >>> third, the code below codes the NSMenuItem to be titled "Documents",
    >>> but it's title is not displayed when it's inserted at index:1 of
    >>> NSMenu *theMenu.  however, if i insert it into [NSApp mainMenu], the
    >>> title "Documents" will display.  this doesn't make sense to me.
    >>
    >> I believe this is a consequence of how you're setting the title and where
    >> you're adding the menu. If you set the title on the documentsItem as well as
    >> the documentsMenu it should appear regardless of where you attach the menu.
    >>
    >>> and finally, i would love to have each of the files and folders to be
    >>> added to the NSMenu with their respected icons.  my attempts to do
    >>> this have failed because i do not know how to get the path of each
    >>> file or folder that is being enumerated.  the code below will warn
    >>> that NSString does not respond to Path.
    >>
    >> You can get the icon for the path with this:
    >>
    >> [[NSWorkspace sharedWorkspace] iconForFile:[documentsDirectory
    >> stringByAppendingPathComponent:fileOrFolder]];
    >>
    >>
    >> Ashley
    >>
    >>
    >