ImageBrowserController.m

/*
     File: ImageBrowserController.m
 Abstract: 
 Abstract:  IKImageBrowserView is a view that can display and browse a
 large amount of images and movies. This sample code demonstrates
 how to use the view in a Cocoa Application.
 
  Version: 1.1
 
 Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple
 Inc. ("Apple") in consideration of your agreement to the following
 terms, and your use, installation, modification or redistribution of
 this Apple software constitutes acceptance of these terms.  If you do
 not agree with these terms, please do not use, install, modify or
 redistribute this Apple software.
 
 In consideration of your agreement to abide by the following terms, and
 subject to these terms, Apple grants you a personal, non-exclusive
 license, under Apple's copyrights in this original Apple software (the
 "Apple Software"), to use, reproduce, modify and redistribute the Apple
 Software, with or without modifications, in source and/or binary forms;
 provided that if you redistribute the Apple Software in its entirety and
 without modifications, you must retain this notice and the following
 text and disclaimers in all such redistributions of the Apple Software.
 Neither the name, trademarks, service marks or logos of Apple Inc. may
 be used to endorse or promote products derived from the Apple Software
 without specific prior written permission from Apple.  Except as
 expressly stated in this notice, no other rights or licenses, express or
 implied, are granted by Apple herein, including but not limited to any
 patent rights that may be infringed by your derivative works or by other
 works in which the Apple Software may be incorporated.
 
 The Apple Software is provided by Apple on an "AS IS" basis.  APPLE
 MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION
 THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS
 FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND
 OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.
 
 IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL
 OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,
 MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED
 AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),
 STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE
 POSSIBILITY OF SUCH DAMAGE.
 
 Copyright (C) 2013 Apple Inc. All Rights Reserved.
 
 */
 
 
#import "ImageBrowserController.h"
#import "ImageBrowserBackgroundLayer.h"
 
 
// -------------------------------------------------------------------------
//  openFiles
//
//  A simple C function that opens NSOpenPanel and returns an array of file paths.
//  It uses uniform type identifiers (UTIs) for proper filtering of image files.
// -------------------------------------------------------------------------
static NSArray* openFiles()
{ 
    // Get a list of extensions to filter in our NSOpenPanel.
    NSOpenPanel* panel = [NSOpenPanel openPanel];
 
    [panel setCanChooseDirectories:YES];    // The user can choose a folder; images in the folder are added recursively.
    [panel setCanChooseFiles:YES];
    [panel setAllowsMultipleSelection:YES];
 
    [panel setAllowedFileTypes:[NSImage imageUnfilteredTypes]];
    NSInteger result = [panel runModal];
    if (result == NSFileHandlingPanelOKButton)
        return [panel URLs];
 
    return nil;
} 
 
 
//==============================================================================
// This is the data source object.
@interface myImageObject : NSObject
{
    NSString* path; 
}
@end
 
@implementation myImageObject
 
// -------------------------------------------------------------------------
//  dealloc
// -------------------------------------------------------------------------
- (void)dealloc
{
    [path release];
    [super dealloc];
}
 
// -------------------------------------------------------------------------
//  setPath:path
//
//  The data source object is just a file path representation
// -------------------------------------------------------------------------
- (void)setPath:(NSString*)inPath
{
    if (path != inPath)
    {
        [path release];
        path = [inPath retain];
    }
}
 
// The required methods of the IKImageBrowserItem protocol.
#pragma mark -
#pragma mark item data source protocol
 
// -------------------------------------------------------------------------
//  imageRepresentationType:
//
//  Set up the image browser to use a path representation.
// -------------------------------------------------------------------------
- (NSString*)imageRepresentationType
{
    return IKImageBrowserPathRepresentationType;
}
 
// -------------------------------------------------------------------------
//  imageRepresentation:
//
//  Give the path representation to the image browser.
// -------------------------------------------------------------------------
- (id)imageRepresentation
{
    return path;
}
 
// -------------------------------------------------------------------------
//  imageUID:
//
//  Use the absolute file path as the identifier.
// -------------------------------------------------------------------------
- (NSString*)imageUID
{
    return path;
}
 
// -------------------------------------------------------------------------
//  imageTitle:
//
//  Use the last path component as the title.
// -------------------------------------------------------------------------
- (NSString*)imageTitle
{
    return [[path lastPathComponent] stringByDeletingPathExtension];
}
 
// -------------------------------------------------------------------------
//  imageSubtitle:
//
//  Use the file extension as the subtitle.
// -------------------------------------------------------------------------
- (NSString*)imageSubtitle
{
    return [path pathExtension];
}
 
@end
 
//==============================================================================
@implementation ImageBrowserController
 
// -------------------------------------------------------------------------
//  dealloc:
// -------------------------------------------------------------------------
- (void)dealloc
{
    [images release];
    [importedImages release];
    [super dealloc];
}
 
// -------------------------------------------------------------------------
//  awakeFromNib:
// -------------------------------------------------------------------------
- (void)awakeFromNib
{
    // Create two arrays : The first is for the data source representation.
    // The second one contains temporary imported images  for thread safeness.
    images = [[NSMutableArray alloc] init];
    importedImages = [[NSMutableArray alloc] init];
    
    // Allow reordering, animations and set the dragging destination delegate.
    [imageBrowser setAllowsReordering:YES];
    [imageBrowser setAnimates:YES];
    [imageBrowser setDraggingDestinationDelegate:self];
    
    // customize the appearance
    [imageBrowser setCellsStyleMask:IKCellsStyleTitled | IKCellsStyleOutlined];
    
    // background layer
    ImageBrowserBackgroundLayer *backgroundLayer = [[[ImageBrowserBackgroundLayer alloc] init] autorelease];
    [imageBrowser setBackgroundLayer:backgroundLayer];
    backgroundLayer.owner = imageBrowser;
    
    //-- change default font 
    // create a centered paragraph style
    NSMutableParagraphStyle *paraphStyle = [[[NSMutableParagraphStyle alloc] init] autorelease];
    [paraphStyle setLineBreakMode:NSLineBreakByTruncatingTail];
    [paraphStyle setAlignment:NSCenterTextAlignment];
    
    NSMutableDictionary *attributes = [[[NSMutableDictionary alloc] init] autorelease]; 
    [attributes setObject:[NSFont systemFontOfSize:12] forKey:NSFontAttributeName]; 
    [attributes setObject:paraphStyle forKey:NSParagraphStyleAttributeName];    
    [attributes setObject:[NSColor blackColor] forKey:NSForegroundColorAttributeName];
    [imageBrowser setValue:attributes forKey:IKImageBrowserCellsTitleAttributesKey];
    
    attributes = [[[NSMutableDictionary alloc] init] autorelease];  
    [attributes setObject:[NSFont boldSystemFontOfSize:12] forKey:NSFontAttributeName]; 
    [attributes setObject:paraphStyle forKey:NSParagraphStyleAttributeName];    
    [attributes setObject:[NSColor whiteColor] forKey:NSForegroundColorAttributeName];
    
    [imageBrowser setValue:attributes forKey:IKImageBrowserCellsHighlightedTitleAttributesKey]; 
    
    //change intercell spacing
    [imageBrowser setIntercellSpacing:NSMakeSize(10, 80)];
    
    //change selection color
    [imageBrowser setValue:[NSColor colorWithCalibratedRed:1 green:0 blue:0.5 alpha:1.0] forKey:IKImageBrowserSelectionColorKey];
    
    //set initial zoom value
    [imageBrowser setZoomValue:0.5];
}
 
// -------------------------------------------------------------------------
//  updateDatasource:
//
//  This is the entry point for reloading image browser data and triggering setNeedsDisplay.
// -------------------------------------------------------------------------
- (void)updateDatasource
{
    // Update the datasource, add recently imported items.
    [images addObjectsFromArray:importedImages];
    
    // Empty the temporary array.
    [importedImages removeAllObjects];
    
    // Reload the image browser, which triggers setNeedsDisplay.
    [imageBrowser reloadData];
}
 
 
#pragma mark -
#pragma mark import images from file system
 
// -------------------------------------------------------------------------
//  isImageFile:filePath
//
//  This utility method indicates if the file located at 'filePath' is
//  an image file based on the UTI. It relies on the ImageIO framework for the
//  supported type identifiers.
//
// -------------------------------------------------------------------------
- (BOOL)isImageFile:(NSString*)filePath
{
    BOOL                isImageFile = NO;
    LSItemInfoRecord    info;
    CFStringRef         uti = NULL;
    
    CFURLRef url = CFURLCreateWithFileSystemPath(NULL, (CFStringRef)filePath, kCFURLPOSIXPathStyle, FALSE);
    
    if (LSCopyItemInfoForURL(url, kLSRequestExtension | kLSRequestTypeCreator, &info) == noErr)
    {
        // Obtain the UTI using the file information.
        
        // If there is a file extension, get the UTI.
        if (info.extension != NULL)
        {
            uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, info.extension, kUTTypeData);
            CFRelease(info.extension);
        }
 
        // No UTI yet
        if (uti == NULL)
        {
            // If there is an OSType, get the UTI.
            CFStringRef typeString = UTCreateStringForOSType(info.filetype);
            if ( typeString != NULL)
            {
                uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassOSType, typeString, kUTTypeData);
                CFRelease(typeString);
            }
        }
        
        // Verify that this is a file that the ImageIO framework supports.
        if (uti != NULL)
        {
            CFArrayRef  supportedTypes = CGImageSourceCopyTypeIdentifiers();
            CFIndex     i, typeCount = CFArrayGetCount(supportedTypes);
 
            for (i = 0; i < typeCount; i++)
            {
                if (UTTypeConformsTo(uti, (CFStringRef)CFArrayGetValueAtIndex(supportedTypes, i)))
                {
                    isImageFile = YES;
                    break;
                }
            }
            
            CFRelease(supportedTypes);
            CFRelease(uti);
        }
    }
    
    CFRelease(url);
    
    return isImageFile;
}
 
// -------------------------------------------------------------------------
//  addAnImageWithPath:path
// -------------------------------------------------------------------------
- (void)addAnImageWithPath:(NSString*)path
{   
    BOOL addObject = NO;
    
    NSDictionary* fileAttribs = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:nil];
    if (fileAttribs)
    {
        // Check for packages.
        if ([NSFileTypeDirectory isEqualTo:[fileAttribs objectForKey:NSFileType]])
        {
            if ([[NSWorkspace sharedWorkspace] isFilePackageAtPath:path] == NO)
                addObject = YES;    // If it is a file, it's OK to add.
        }
        else
        {
            addObject = YES;    // It is a file, so it's OK to add.
        }
    }
    
    if (addObject && [self isImageFile:path])
    {
        // Add a path to the temporary images array.
        myImageObject* p = [[myImageObject alloc] init];
        [p setPath:path];
        [importedImages addObject:p];
        [p release];
    }
}
 
// -------------------------------------------------------------------------
//  addImagesWithPath:path:recursive
// -------------------------------------------------------------------------
- (void)addImagesWithPath:(NSString*)path
{
    BOOL dir;
    [[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&dir];
    
    if (dir)
    {
        NSInteger i, n;
        
        NSArray* content = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:nil];
        n = [content count];
 
        // Parse the directory content.
        for (i = 0; i < n; i++)
        {
            [self addAnImageWithPath:[path stringByAppendingPathComponent:[content objectAtIndex:i]]];
        }
    }
    else
    {
        [self addAnImageWithPath:path];
    }
}
 
// -------------------------------------------------------------------------
//  addImagesWithPaths:paths
//
//  Performed in an independent thread, parse all paths in "paths" and
//  add these paths in the temporary images array.
// -------------------------------------------------------------------------
- (void)addImagesWithPaths:(NSArray*)paths
{   
    NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
    [paths retain];
 
    NSInteger i, n;
    n = [paths count];
    for (i = 0; i < n; i++)
    {
        NSURL *url = [paths objectAtIndex:i];
        [self addImagesWithPath:[url path]];
    }
 
    // Update the data source in the main thread.
    [self performSelectorOnMainThread:@selector(updateDatasource) withObject:nil waitUntilDone:YES];
 
    [paths release];
    [pool release];
}
 
 
#pragma mark -
#pragma mark actions
 
// -------------------------------------------------------------------------
//  addImageButtonClicked:sender
//
//  The user clicked the Add button.d
// -------------------------------------------------------------------------
- (IBAction)addImageButtonClicked:(id)sender
{
    NSArray* path = openFiles();
    if (path)
    { 
        // launch import in an independent thread
        [NSThread detachNewThreadSelector:@selector(addImagesWithPaths:) toTarget:self withObject:path];
    }
}
 
// -------------------------------------------------------------------------
//  addImageButtonClicked:sender
//
//  Action called when the zoom slider changes.
// ------------------------------------------------------------------------- 
- (IBAction)zoomSliderDidChange:(id)sender
{
    // update the zoom value to scale images
    [imageBrowser setZoomValue:[sender floatValue]];
    
    // redisplay
    [imageBrowser setNeedsDisplay:YES];
}
 
 
#pragma mark -
#pragma mark IKImageBrowserDataSource
 
// Implement the image browser  data source protocol .
// The data source representation is a simple mutable array.
 
// -------------------------------------------------------------------------
//  numberOfItemsInImageBrowser:view
// ------------------------------------------------------------------------- 
- (NSUInteger)numberOfItemsInImageBrowser:(IKImageBrowserView*)view
{
    // The item count to display is the datadsource item count.
    return [images count];
}
 
// -------------------------------------------------------------------------
//  imageBrowser:view:index:
// ------------------------------------------------------------------------- 
- (id)imageBrowser:(IKImageBrowserView *) view itemAtIndex:(NSUInteger) index
{
    return [images objectAtIndex:index];
}
 
 
// Implement some optional methods of the image browser  datasource protocol to allow for removing and reodering items.
 
// -------------------------------------------------------------------------
//  removeItemsAtIndexes:
//
//  The user wants to delete images, so remove these entries from the data source.  
// ------------------------------------------------------------------------- 
- (void)imageBrowser:(IKImageBrowserView*)view removeItemsAtIndexes: (NSIndexSet*)indexes
{
    [images removeObjectsAtIndexes:indexes];
}
 
// -------------------------------------------------------------------------
//  moveItemsAtIndexes:
//
//  The user wants to reorder images, update the datadsource and the browser
//  will reflect our changes.
// ------------------------------------------------------------------------- 
- (BOOL)imageBrowser:(IKImageBrowserView*)view moveItemsAtIndexes: (NSIndexSet*)indexes toIndex:(unsigned int)destinationIndex
{
    NSUInteger      index;
    NSMutableArray* temporaryArray;
 
    temporaryArray = [[[NSMutableArray alloc] init] autorelease];
 
    // First remove items from the data source and keep them in a temporary array.
    for (index = [indexes lastIndex]; index != NSNotFound; index = [indexes indexLessThanIndex:index])
    {
        if (index < destinationIndex)
          destinationIndex --;
 
        id obj = [images objectAtIndex:index];
        [temporaryArray addObject:obj];
        [images removeObjectAtIndex:index];
    }
 
    // Then insert the removed items at the appropriate location.
    NSUInteger n = [temporaryArray count];
    for (index = 0; index < n; index++)
    {
        [images insertObject:[temporaryArray objectAtIndex:index] atIndex:destinationIndex];
    }
 
    return YES;
}
 
 
#pragma mark -
#pragma mark drag n drop 
 
// -------------------------------------------------------------------------
//  draggingEntered:sender
// ------------------------------------------------------------------------- 
- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
{
    return NSDragOperationCopy;
}
 
// -------------------------------------------------------------------------
//  draggingUpdated:sender
// ------------------------------------------------------------------------- 
- (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender
{
    return NSDragOperationCopy;
}
 
// -------------------------------------------------------------------------
//  performDragOperation:sender
// ------------------------------------------------------------------------- 
- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
{
    NSData*         data = nil;
    NSPasteboard*   pasteboard = [sender draggingPasteboard];
 
    // Look for paths on the pasteboard.
    if ([[pasteboard types] containsObject:NSFilenamesPboardType]) 
        data = [pasteboard dataForType:NSFilenamesPboardType];
 
    if (data)
    {
        NSString* errorDescription;
        
        // Retrieve  paths.
        NSArray* filenames = [NSPropertyListSerialization propertyListFromData:data 
                                mutabilityOption:kCFPropertyListImmutable 
                                format:nil 
                                errorDescription:&errorDescription];
 
        // Add paths to the data source.
        [self addImagesWithPaths:filenames];
        
        // Make the image browser reload the data source.
        [self updateDatasource];
    }
 
    // Accept the drag operation.
    return YES;
}
 
@end