| Index | Submit class | Submit snippet | Submission feed | List |

UKFilePathView

Language: Objective-C, Author: Uli Kusterer
License: MIT/X11

An NSView that displays a file path. This looks kind of like how Sherlock displays the location of a file. Basically you get each folder's display name with its icon in front of it, and little grey triangles between them, and you get the icon of each file or folder and its display name.

If the path is too long, this takes items out of the middle and displays an ellipsis character (...) instead.

You can also right-click or control-click on this to get a contextual menu that contains "Reveal in Finder" and "Show Real Names" menu choices. Real names displays the actual path, starting with "/", and using the actual file names instead of their display names. As a shortcut, double-clicking this view is the same as "Reveal in Finder".

Finally, this also lets you hook up buttons to the view to let you choose an existing, or new file or folder name.

UKFilePathView source preview

//
//  UKFilePathView.m
//  Shovel
//
//  Created by Uli Kusterer on Thu Mar 25 2004.
//  Copyright (c) 2004 M. Uli Kusterer. All rights reserved.
//

#import "UKFilePathView.h"
#import "UKGraphics.h"


@implementation UKFilePathView

-(id)  initWithFrame: (NSRect)frame
{
    self = [super initWithFrame:frame];
    if( self )
  {
        filePath = nil;      // Means "none".
    noDisplayNames = NO;  // Show display names by default.
    canChooseFiles = YES;
    canChooseDirectories = NO;
    treatsFilePackagesAsDirectories = NO;
    placeholderString = [NSLocalizedString(@"<None>", @"default UKFilePathView placeholder") retain];
    }
    return self;
}


-(void)  dealloc
{
  [filePath release];
  [placeholderString release];
  [types release];
  
  [super dealloc];
}


-(void) drawRect:(NSRect)rect
{
  NSPoint      pos = { 0, 0 };
  NSDictionary*   attribs = [NSDictionary dictionaryWithObjectsAndKeys: [NSFont systemFontOfSize: [NSFont systemFontSize]], NSFontAttributeName, [NSColor controlTextColor], NSForegroundColorAttributeName, nil];
  NSDictionary*   disAttribs = [NSDictionary dictionaryWithObjectsAndKeys: [NSFont systemFontOfSize: [NSFont systemFontSize]], NSFontAttributeName, [NSColor disabledControlTextColor], NSForegroundColorAttributeName, nil];
    NSMutableArray* components = [NSMutableArray array];
    NSMutableArray* icons = [NSMutableArray array];
    NSMutableArray* paths = [NSMutableArray array];
  NSSize      theSize = [@"foo" sizeWithAttributes: attribs];
  NSEnumerator*   enny = nil;
  NSString*    currName;
  NSImage*    emptyImg = [[[NSImage alloc] initWithSize: NSMakeSize(1,1)] autorelease];
  
  // Draw border and make sure text is vertically centered:
  UKDrawDropHighlightedEditableWhiteBezel( drawDropHighlight, action != 0, [self bounds], [self bounds] );
  pos.y += truncf(([self bounds].size.height -theSize.height) /2);
  
  // If no path specified, show "none":
  if( filePath == nil )
  {
    [placeholderString drawAtPoint: NSMakePoint(pos.x +4, pos.y +2) withAttributes: disAttribs];
    return;
  }
  
  // Build the display path and list our icons:
  enny = [[filePath pathComponents] objectEnumerator];
  NSMutableString*  currPath = [NSMutableString string];
  while( (currName = [enny nextObject]) )
  {
    int pln = [currPath length];
    if( pln == 0 || [currPath characterAtIndex: pln -1] != '/' )
      [currPath appendString: @"/"];
    if( ![currName isEqualToString: @"/"] )
      [currPath appendString: currName];
    [icons addObject: [[NSWorkspace sharedWorkspace] iconForFile: currPath]];
    [paths addObject: [[currPath copy] autorelease]];
    if( !noDisplayNames )  // We're showing display names?
    {
      [components addObject: [[NSFileManager defaultManager] displayNameAtPath: currPath]];
      if( [currPath isEqualToString: @"/Volumes"] )
      {
        [components removeObjectsInRange: NSMakeRange(0,2)];
        [icons removeObjectsInRange: NSMakeRange(0,2)];
        [paths removeObjectsInRange: NSMakeRange(0,2)];
      }
    }
        else
      [components addObject: currName];
  }

  enny = [components objectEnumerator];
  int        componentsToGo = [components count];
  
  // Calculate width of displayed path:
  theSize.width = 0;
  theSize.height = 0;
  while( (currName = [enny nextObject]) )
  {
    theSize.width += [currName sizeWithAttributes: attribs].width +UK_PATH_NAME_TOTAL_HMARGIN
              +UK_PATH_ICON_IMG_WIDTH +UK_PATH_ICON_NAME_HDISTANCE;
    if( --componentsToGo > 0 )
      theSize.width += UK_PATH_ARROW_IMG_WIDTH;
  }
  
  // If it's wider than our box, start taking components out of the middle:
  if( [components count] > 2 && (theSize.width > [self bounds].size.width) )
  {
    int middleEntry = ([components count] /2) -1;
    
    if( (middleEntry * 2) < [components count] )
      middleEntry++;
    
    // Replace the middle-most entry with an ellipsis ("..."):
    theSize.width -= [[components objectAtIndex: middleEntry] sizeWithAttributes: attribs].width +UK_PATH_NAME_TOTAL_HMARGIN +UK_PATH_ICON_IMG_WIDTH +UK_PATH_ICON_NAME_HDISTANCE;
    [components replaceObjectAtIndex: middleEntry withObject: UK_PATH_ELLIPSIS];
    theSize.width += [UK_PATH_ELLIPSIS sizeWithAttributes: attribs].width +UK_PATH_NAME_TOTAL_HMARGIN;
    
    while( [components count] > 3 && (theSize.width > [self bounds].size.width) )
    {
      [components removeObjectAtIndex: middleEntry];  // Remove "...".
      [icons removeObjectAtIndex: middleEntry];  // Remove empty icon for ellipsis.
      [paths removeObjectAtIndex: middleEntry];  // Remove empty path for ellipsis.
      middleEntry = ([components count] /2);
      theSize.width -= [[components objectAtIndex: middleEntry] sizeWithAttributes: attribs].width +UK_PATH_NAME_TOTAL_HMARGIN +UK_PATH_ARROW_IMG_WIDTH +UK_PATH_ICON_IMG_WIDTH +UK_PATH_ICON_NAME_HDISTANCE;
      [components replaceObjectAtIndex: middleEntry withObject: UK_PATH_ELLIPSIS];
      [icons replaceObjectAtIndex: middleEntry withObject: emptyImg];
      [paths replaceObjectAtIndex: middleEntry withObject: @""];
    }
  }
  
  if( [components count] == 3 && theSize.width > [self bounds].size.width )  // Still wider?
  {
    // Remove final two components so we only show file icon and name:
    [components removeObjectAtIndex: 1];
    [icons removeObjectAtIndex: 1];
    [paths removeObjectAtIndex: 1];
    [components removeObjectAtIndex: 0];
    [icons removeObjectAtIndex: 0];
    [paths removeObjectAtIndex: 0];
  }
  
  // Draw components that are left:
  NSImage*      theIcon = nil;
  NSEnumerator*    iconEnny = [icons objectEnumerator];
    NSString*           thePath = nil;
  NSEnumerator*    pathEnny = [paths objectEnumerator];
  enny = [components objectEnumerator];
  componentsToGo = [components count];
  while( (currName = [enny nextObject]) )
  {
    theIcon = [iconEnny nextObject];
    thePath = [pathEnny nextObject];
    
    theSize = [currName sizeWithAttributes: attribs];
    theSize.width += UK_PATH_NAME_TOTAL_HMARGIN;
    theSize.height += UK_PATH_NAME_TOTAL_VMARGIN;
    
        BOOL        exists = [[NSFileManager defaultManager] fileExistsAtPath: thePath];
        
        //NSLog(@"%d %@ (%@)", (int)exists, currName, thePath );
        
    if( ![UK_PATH_ELLIPSIS isEqualToString: currName] )
    {
      [theIcon setSize: NSMakeSize(UK_PATH_ICON_IMG_WIDTH,UK_PATH_ICON_IMG_WIDTH)];
      [theIcon dissolveToPoint: NSMakePoint(pos.x +UK_PATH_NAME_LEFT_MARGIN, pos.y +UK_PATH_NAME_BOTTOM_MARGIN) fraction: (exists ? 1 : 0.3)];
      
      pos.x += UK_PATH_ICON_IMG_WIDTH +UK_PATH_ICON_NAME_HDISTANCE;
    }
    
    [currName drawAtPoint: NSMakePoint(pos.x +UK_PATH_NAME_LEFT_MARGIN, pos.y +UK_PATH_NAME_BOTTOM_MARGIN) withAttributes: (exists ? attribs : disAttribs)];
    
    pos.x += theSize.width;
    if( --componentsToGo > 0 )
    {
      [[NSImage imageNamed: @"PathArrow.tiff"] dissolveToPoint: pos fraction: 1];
      pos.x += UK_PATH_ARROW_IMG_WIDTH;
    }
  }
}

-(NSString *)  filePath
{
    return filePath;
}

-(void)  setFilePath: (NSString *)newFilePath
{
    if( filePath != newFilePath )
  {
    [filePath release];
    filePath = [newFilePath retain];
    [self setNeedsDisplay: YES];
  }
}


-(void) reshowDisplayNames: (id)sender
{
  noDisplayNames = NO;
  [self setNeedsDisplay: YES];
}


-(void)  showRealNames: (id)sender
{
  if( !noDisplayNames )
  {
    noDisplayNames = YES;
    [self setNeedsDisplay: YES];
    [NSTimer scheduledTimerWithTimeInterval: 5  // 5 secs should be enough.
          target: self selector:@selector(reshowDisplayNames:)
          userInfo:nil repeats: NO];
  }
}

-(void)  toggleShowRealNames: (id)sender
{
  noDisplayNames = !noDisplayNames;
  [self setNeedsDisplay: YES];
}


-(void)  revealInFinder: (id)sender
{
  [[NSWorkspace sharedWorkspace] selectFile: filePath inFileViewerRootedAtPath: @""];
}


-(BOOL)  validateMenuItem: (id<NSMenuItem>)item
{
  if( [item action] == @selector(revealInFinder:)
    || [item action] == @selector(showRealNames:) )
    return filePath != nil;
  else if( [item action] == @selector(toggleShowRealNames:) )
  {
    [item setState: noDisplayNames];
    return filePath != nil;
  }
  else
    return NO;
}


// Upon a click, we shortly toggle from display to file names and back:
-(void) mouseDown: (NSEvent*)evt
{
  if( filePath && [evt clickCount] == 2 )
    [[NSWorkspace sharedWorkspace] selectFile: filePath inFileViewerRootedAtPath: @""];
}

+(NSMenu*)  defaultMenu
{
  NSMenu*    theMenu = [[[NSMenu alloc] initWithTitle: @"Contextual Menu"] autorelease];
  
  [theMenu addItemWithTitle: NSLocalizedString(@"Reveal in Finder",@"UKFilePathView contextual menu item")
        action: @selector(revealInFinder:) keyEquivalent: @""];
  [theMenu addItemWithTitle: NSLocalizedString(@"Show Real File Names",@"UKFilePathView contextual menu item")
        action: @selector(toggleShowRealNames:) keyEquivalent: @""];
  
  return theMenu;
}


-(IBAction)      pickFile: (id)sender
{
  NSOpenPanel*  op = [NSOpenPanel openPanel];
  
  [op setAllowsMultipleSelection: NO];
  [op setCanChooseFiles: canChooseFiles];
  [op setCanChooseDirectories: canChooseDirectories];
  [op setTreatsFilePackagesAsDirectories: treatsFilePackagesAsDirectories];
  
  [op beginSheetForDirectory: [filePath stringByDeletingLastPathComponent]
      file: filePath types: types modalForWindow: [self window]
      modalDelegate: self didEndSelector: @selector(openPanelDidEnd:returnCode:contextInfo:) contextInfo: self];

}

-(IBAction)      pickNewFile: (id)sender
{
  NSSavePanel*  op = [NSSavePanel savePanel];
  
  [op setTreatsFilePackagesAsDirectories: treatsFilePackagesAsDirectories];
  
  [op beginSheetForDirectory: [filePath stringByDeletingLastPathComponent]
      file: filePath modalForWindow: [self window]
      modalDelegate: self didEndSelector: @selector(openPanelDidEnd:returnCode:contextInfo:) contextInfo: self];

}

-(void)  openPanelDidEnd: (NSOpenPanel*)sheet returnCode: (int)returnCode contextInfo: (void*)contextInfo
{
  if( returnCode == NSOKButton )
  {
    [self setFilePath: [sheet filename]];
    
    if( [target respondsToSelector: action] )
      [target performSelector: action withObject: self];
  }
}


-(IBAction)    pickNoFile: (id)sender
{
  [self setFilePath: nil];
  
  if( [target respondsToSelector: action] )
    [target performSelector: action withObject: self];
}


// ---------------------------------------------------------- 
// - types:
// ---------------------------------------------------------- 
- (NSArray *) types
{
    return types; 
}

// ---------------------------------------------------------- 
// - setTypes:
// ---------------------------------------------------------- 
- (void) setTypes: (NSArray *) theTypes
{
    if (types != theTypes) {
        [types release];
        types = [theTypes retain];
    }
}


// ---------------------------------------------------------- 
// - canChooseFiles:
// ---------------------------------------------------------- 
- (BOOL) canChooseFiles
{

    return canChooseFiles;
}

// ---------------------------------------------------------- 
// - setCanChooseFiles:
// ---------------------------------------------------------- 
- (void) setCanChooseFiles: (BOOL) flag
{
        canChooseFiles = flag;
}

// ---------------------------------------------------------- 
// - canChooseDirectories:
// ---------------------------------------------------------- 
- (BOOL) canChooseDirectories
{

    return canChooseDirectories;
}

// ---------------------------------------------------------- 
// - setCanChooseDirectories:
// ---------------------------------------------------------- 
- (void) setCanChooseDirectories: (BOOL) flag
{
        canChooseDirectories = flag;
}

// ---------------------------------------------------------- 
// - treatsFilePackagesAsDirectories:
// ---------------------------------------------------------- 
- (BOOL) treatsFilePackagesAsDirectories
{

    return treatsFilePackagesAsDirectories;
}

// ---------------------------------------------------------- 
// - setTreatsFilePackagesAsDirectories:
// ---------------------------------------------------------- 
- (void) setTreatsFilePackagesAsDirectories: (BOOL) flag
{
        treatsFilePackagesAsDirectories = flag;
}


-(NSString *)  stringValue
{
  return [self filePath];
}


-(void)      setStringValue: (NSString*)s
{
  [self setFilePath: s];
}


-(void)  concludeDragOperation: (id <NSDraggingInfo>)sender
{
  drawDropHighlight = NO;
  [self setNeedsDisplay: YES];
}

-(NSDragOperation)  draggingEntered: (id <NSDraggingInfo>)sender
{
  drawDropHighlight = YES;
  [self setNeedsDisplay: YES];

  return NSDragOperationLink;
}

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

-(BOOL)  performDragOperation: (id <NSDraggingInfo>)sender
{
  NSPasteboard*  pb = [sender draggingPasteboard];
  NSString*    type = [pb availableTypeFromArray: [NSArray arrayWithObjects: NSFilenamesPboardType, NSFilesPromisePboardType, nil]];
  NSArray*    arr = [pb propertyListForType: type];
  NSString*    thePath = [arr objectAtIndex: 0];
  NSString*    fileExtension = [thePath pathExtension];
  BOOL      isDir = NO;
  
  [[NSFileManager defaultManager] fileExistsAtPath: thePath isDirectory: &isDir];
  if( [types containsObject: [fileExtension lowercaseString]]
    && ((!isDir && canChooseFiles) || (isDir && canChooseDirectories)) )
  {
    [self setFilePath: thePath];
    [target performSelector: action withObject: self];
  }
  else
    NSBeep();
  
  return YES;
}


-(void)  draggingExited:(id <NSDraggingInfo>)sender
{
  drawDropHighlight = NO;
  [self setNeedsDisplay: YES];
}


// ---------------------------------------------------------- 
// - action:
// ---------------------------------------------------------- 
- (SEL) action
{
    return action;
}

// ---------------------------------------------------------- 
// - setAction:
// ---------------------------------------------------------- 
- (void) setAction: (SEL) theAction
{
  action = theAction;
  
  if( action != 0 )
    [self registerForDraggedTypes: [NSArray arrayWithObjects: NSFilenamesPboardType, NSFilesPromisePboardType, nil]];
  else
    [self unregisterDraggedTypes];
}

// ---------------------------------------------------------- 
// - target:
// ---------------------------------------------------------- 
- (id) target
{
    return target; 
}

// ---------------------------------------------------------- 
// - setTarget:
// ---------------------------------------------------------- 
- (void) setTarget: (id) theTarget
{
        target = theTarget;
}

-(void)      setPlaceholderString: (NSString*)string
{
  if( string != placeholderString )
  {
    [placeholderString release];
    placeholderString = [string retain];
  }
}

-(NSString*)  placeholderString
{
  return placeholderString;
}



@end

UKFilePathView header preview

//
//  UKFilePathView.h
//  Shovel
//
//  Created by Uli Kusterer on Thu Mar 25 2004.
//  Copyright (c) 2004 M. Uli Kusterer. All rights reserved.
//

#import <AppKit/AppKit.h>


/*
  An NSView that displays a file path. This looks kind of like how Sherlock
  displays the location of a file. Basically you get each folder's display
  name with its icon in front of it, and little grey triangles between
  them, and you get the icon of the file or folder at the end and its display
    name.
  
  If the path is too long, this takes items out of the middle and displays
  an ellipsis character (...) instead.
  
  You can also right-click or control-click on this to get a contextual menu
  that contains "Reveal in Finder" and "Show Real Names" menu choices. Real
  names displays the actual path, starting with "/", and using the actual
  file names instead of their display names. As a shortcut, double-clicking
  this view is the same as "Reveal in Finder".
  
  This needs:
    - PathArrow.tiff (for the little triangle)
    - UKGraphics.h/.m (for drawing the bezel around the view)
*/


@interface UKFilePathView : NSView
{
  NSString*    filePath;      // The path to be displayed.
  BOOL      noDisplayNames;    // Show actual names, not display names.
  BOOL      canChooseFiles;            // Handed on directly to the open panel.
  BOOL      canChooseDirectories;        // Handed on directly to the open panel.
  BOOL      treatsFilePackagesAsDirectories;  // Handed on directly to the open/save panels.
  NSArray*    types;                // Handed on directly to the open panel.
  SEL        action;
  id        target;
  BOOL      drawDropHighlight;
  NSString*    placeholderString;  // A placeholder to show when path is NIL. Defaults to "none".
}

-(NSString *)  filePath;
-(void)      setFilePath: (NSString *)newFilePath;

-(id)      target;
-(void)      setTarget: (id) theTarget;

-(SEL)      action;
-(void)      setAction: (SEL) theAction;

-(void)      revealInFinder: (id)sender;
-(void)      showRealNames: (id)sender;
-(void)      toggleShowRealNames: (id)sender;

-(NSString *)  stringValue;          // same as filePath.
-(void)      setStringValue: (NSString*)s;  // same as setFilePath.

-(void)      setPlaceholderString: (NSString*)string;
-(NSString*)  placeholderString;


// UI for changing value:
-(IBAction)    pickFile: (id)sender;      // NSOpenPanel. Chooses existing files.
-(IBAction)    pickNewFile: (id)sender;    // NSSavePanel. Lets the user specify name and location for new files.
-(IBAction)    pickNoFile: (id)sender;      // Sets the file path to NIL.

// Getters/setters for the NSOpenPanel properties:
-(BOOL) canChooseFiles;
-(void) setCanChooseFiles: (BOOL)flag;

-(BOOL) canChooseDirectories;
-(void) setCanChooseDirectories: (BOOL)flag;

-(NSArray*)  types;
-(void)  setTypes: (NSArray*)theTypes;

// Getters/setters for NSOpenPanel/NSSavePanel properties:
-(BOOL) treatsFilePackagesAsDirectories;
-(void) setTreatsFilePackagesAsDirectories: (BOOL)flag;

@end


// What to display instead of items that had to be left out because there wasn't room:
#define UK_PATH_ELLIPSIS      @"..."

// Some constants we use for this view's metrics:
#define UK_PATH_NAME_LEFT_MARGIN  4
#define UK_PATH_NAME_RIGHT_MARGIN  4
#define UK_PATH_NAME_TOP_MARGIN    2
#define UK_PATH_NAME_BOTTOM_MARGIN  2
#define UK_PATH_NAME_TOTAL_VMARGIN  (UK_PATH_NAME_TOP_MARGIN +UK_PATH_NAME_BOTTOM_MARGIN)
#define UK_PATH_NAME_TOTAL_HMARGIN  (UK_PATH_NAME_LEFT_MARGIN +UK_PATH_NAME_RIGHT_MARGIN)
#define UK_PATH_ARROW_IMG_WIDTH    16
#define UK_PATH_ICON_IMG_WIDTH    16
#define UK_PATH_ICON_NAME_HDISTANCE 2




Download Archive

Compatible with:


Comments

Name

Website

Do you hate spammers? (Answer "Yes")