News Software Download Buy Now About Us Developers Contact
software TOC | PREV | NEXT
//
// GifController.m (c) 1998 Andrew C Stone
// andrew@stone.com (505) 345-4800 http://www.stone.com - Create(TM)
//
// THE BRAINS BEHIND GIFfun!
// Permission is granted to use this code, but please retain credit
// And as always, CAVEAT EMPTOR!
// formatted with tabs for instant web page making, make your window wide!

#import "
GifController.h"

#import "
GIFfun.h"
#import "
GifInfo.h"
#import "
GifWell.h"
#import "Preferences.subproj/
GIFPrefs.h"
#import "Info.subproj/
InfoAndHelp.h"
#import "TIFFtoGIF.subproj/
GimmeGIF.h"
#import "SDTask.subproj/
SDLogTask.h"

//
// If you code PASCAL style (top down), then you'll
// need to prototype methods which are defined
// later in the source file after a previous usage
//

@interface GifController(ShushMyLittlePrettyCompiler)
- (void)loadDefaults;
- (void)tryAndDoEverything:(NSString *)path;
- (void)setLookAndFeelOfTableView;
- (void)readSettingsFromDict:(NSDictionary *)dict;
@end

@implementation GifController

//
// Application Delegate methods
//

- (void)applicationWillFinishLaunching:(NSNotification *)notification
{
// we are the recipient of the service calls outlined in CustomInfo.plist:
    [[notification object] setServicesProvider:self];

// force an +initialize and thus registry...
    [GIFPrefs sharedDefaults];    

// find the executable in our Resouces directory:
    {
        NSString *path;
#ifdef WIN32
        NSString *ext = @"exe";
#else
        NSString *ext = @"";
#endif
        if ((path = [[NSBundle mainBundle] pathForResource:@"WhirlGif" ofType:ext]))
            whirlgif = [[NSString alloc]initWithString:path];
        else NSLog(@"Cannot find WhirlGif executable...");
    }
}

//
// We make sure that we are initialized, even if launched from services or double-click
//

- (void)doInitThingsOnce
{
    static BOOL first = YES;
    if (first) {
        first = NO;
    // let's restore the user's previous state:
        [self loadDefaults];
        [window setFrameUsingName:NSStringFromClass([self class])];
        [window setFrameAutosaveName:NSStringFromClass([self class])];

        [self setLookAndFeelOfTableView];
    }
}

//
// This method is all we need to implement to open files by double-clicking:
//

- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename;
{
    // in case we don't finish launching!
    [self doInitThingsOnce];
    [self tryAndDoEverything:filename];
    return YES;
}

- (void)applicationDidFinishLaunching:(NSNotification *)notification
{
    [self doInitThingsOnce];
}

//
// User Preferences
// deal with users preferences by remembering whatever they had before
// At "Create A GIF time" we save the current state
//
- (void)loadDefaults
{
    NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
    [colorIndexField setStringValue:[ud stringForKey:DEF_TRANS]];
    [disposalMatrix selectCellWithTag:[ud integerForKey:DEF_DISPOSAL]];
    [loopField setIntValue:[ud integerForKey:DEF_LOOPS]];
    [loopMatrix selectCellWithTag:[ud integerForKey:DEF_LOOP_METHOD]];
}

//
// Factoring the code into small chunks which all methods
// pass through the same call stack:
//

//
// Is a file possible to be read as an image?
//

- (BOOL)isImageFile:(NSString *)path
{
    NSArray *types = [NSImage imageFileTypes];
    int i = [types count];
    while (i--) if ([[path pathExtension] caseInsensitiveCompare:[types objectAtIndex:i]] == NSOrderedSame) return YES;
    return NO;
}

- (BOOL)isGifFile:(NSString *)path
{
    BOOL isDir;
    return ([[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDir] && !isDir && (
        ([[path pathExtension] caseInsensitiveCompare:@"gif"] == NSOrderedSame)

#if !defined(OS_API)
        || [self isImageFile:path]
#endif
    ));
}


- (int)tryAndLoadPath:(NSString *)path
{
    if ([self isGifFile:path]) return [self addGifFile:path];
    else return [self loadGifsInDirectory:path];
}

- (void)tryAndDoEverything:(NSString *)path
{
    if ([self tryAndLoadPath:path]) [self mergeGifs:self];
}


// Service methods: (see CustomInfo.plist)

- (void)loadAFileOrDirectory:(NSPasteboard *)pboard userData:(NSString *)ud
error:(NSString **)msg
{
    NSArray *fileNames;
    int i, count;

    fileNames = [pboard propertyListForType:NSFilenamesPboardType];
    count = [fileNames count];

// if just one FILE, we'll add it to the list:
    if (count == 1) {
        [self tryAndDoEverything:[fileNames objectAtIndex:0]];
    } else {
        for (i = 0; i < [fileNames count]; i++) {
            NSString *n = [fileNames objectAtIndex:i];
            if ([self isGifFile:n]) [self addGifFile:n];
    }
    [self mergeGifs:self];
    }
}

//

// Temporary Directory stuff: useful code.
//

BOOL directoryOK(NSString *path)
{
    BOOL isDirectory;
    NSFileManager *fileManager = [NSFileManager defaultManager];
    if (![fileManager fileExistsAtPath:path isDirectory:&isDirectory] || !isDirectory) {
        NSDictionary *dict = [NSDictionary dictionaryWithObject:
            [NSNumber numberWithUnsignedLong:0777]
            forKey:NSFilePosixPermissions];
        if (![fileManager createDirectoryAtPath:path attributes:dict])
            return NO;
    }
    return YES;
}

NSString * existingPath(NSString *path)
{
    while (path && ![path isEqualToString:@""]
        && ![[NSFileManager defaultManager] fileExistsAtPath:path])
        path = [path stringByDeletingLastPathComponent];
    return path;    
}

NSArray *directoriesToAdd(NSString *path, NSString *existing)
{
    NSMutableArray *a = [NSMutableArray arrayWithCapacity:4];
    if (path != nil && existing != nil) {
        while (![path isEqualToString:existing]) {
            [a insertObject:[path lastPathComponent] atIndex:0];
            path = [path stringByDeletingLastPathComponent];
        }
    }
    return a;
}

// this will go up the path until it finds an existing directory
// and will add each subpath and return YES if succeeds, NO if fails:

- (BOOL)createWritableDirectory:(NSString *)path
{
    BOOL isDirectory;
    NSFileManager *fileManager = [NSFileManager defaultManager];
    if ([fileManager fileExistsAtPath:path isDirectory:&isDirectory]
        && isDirectory && [fileManager isWritableFileAtPath:path])
        return YES; // no work to do
    else {
        NSString *existing = existingPath(path);
        NSArray *dirsToAdd = directoriesToAdd(path,existing);
        int i;
        BOOL good = YES;
        for (i = 0; i < [dirsToAdd count]; i++) {
            existing = [existing stringByAppendingPathComponent:
                [dirsToAdd objectAtIndex:i]];
            if (!directoryOK(existing)) {
                good = NO;
                break;
            }
        }
        return good;
    }
}


#define GIF_FILE_NAME    @"GF.gif"

- (NSString *)nextUniqueNameUsing:(NSString *)templatier
{
    static int unique = 1;
    NSString *tempName = nil;
    do {
        tempName =[NSString stringWithFormat:@"%@_%d.%@",
        [templatier stringByDeletingPathExtension],unique++,
        [templatier pathExtension]];
    } while ([[NSFileManager defaultManager] fileExistsAtPath:tempName]);

    return tempName;
}

- (NSString *)temporaryDirectory
{
    NSString *tempDir =[[NSTemporaryDirectory()
        stringByAppendingPathComponent:
        [[NSProcessInfo processInfo] processName]]
        stringByAppendingPathComponent:NSUserName()];

    if (! [self createWritableDirectory:tempDir]) {
        NSLog(@"Couldn't create %@, using %@",tempDir,
            NSTemporaryDirectory());
        tempDir = NSTemporaryDirectory();
    }
    return tempDir;
}

- (NSString *)scratchFolder
{
    NSString *s = [[NSUserDefaults standardUserDefaults]stringForKey:DEF_DIRECTORY];
    if (!s || [s isEqualToString:@""] ||
        ![self createWritableDirectory:s])
            s = [self temporaryDirectory];
    return s;
}

// if the file package has 00, 01, 02 endings, then we should sort accordingly:

- (NSArray *)sortFiles:(NSArray *)dp
{
    return [dp sortedArrayUsingSelector:@selector(compare:)];
}

- (int)loadGifsInDirectory:(NSString *)directory;
{
    int i;
    int count;
    NSArray *dp;
    NSString *shortname;
    NSString *path;
    NSDictionary *dict;
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSString *settingsPath = [directory
        stringByAppendingPathComponent:GIFFUN_SETTINGS_FILE];
    // start from scratch:
    [self clearGifs:self];

    dp = [fileManager directoryContentsAtPath:directory];
    dp = [self sortFiles:dp];
    if ((count = [dp count])>0) {
        for (i = 0; i < count; i++) {
            shortname = [dp objectAtIndex:i];
            path = [directory stringByAppendingPathComponent:shortname];
            if ([self isGifFile:path]) {
                [self addGifFile:path];
            }
        }
    }
//
// Now, if we were the ones who saved these GIFS
// then we'll find a file which has our settings in dictionary form
// We'll try and load it, but we don't care if the dict is not there..
//

    if ([[NSFileManager defaultManager] isReadableFileAtPath:settingsPath]
     && ((dict = [NSDictionary dictionaryWithContentsOfFile:settingsPath]) != nil)) {
        [self readSettingsFromDict:dict];
    }
    return [gifFileArray count];
}

- (int)addGifFile:(NSString *)aFile
{
    NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
// On OpenStep (ie OS_API defined) this will never be the case:
    BOOL needsConversion = ([[aFile pathExtension] caseInsensitiveCompare:@"gif"] != NSOrderedSame);

    if (needsConversion) {
// /somewhere/something.tiff -> /tmp/GIFfun/andrew/something.gif
        NSString *gifFile = [[self temporaryDirectory]
            stringByAppendingPathComponent:
            [[[aFile lastPathComponent]stringByDeletingPathExtension]
            stringByAppendingPathExtension:@"gif"]];

        if ([[GimmeGIF sharedInstance]
            convertImageFile:aFile toGIFFile:gifFile])
            aFile = [[gifFile copy]autorelease];
        else return 0;
    }

    if (!gifFileArray)
        gifFileArray = [[NSMutableArray alloc]initWithCapacity:1];
    [gifFileArray addObject:
        [[GifInfo alloc]initWithPath:aFile
            andDelay:[ud integerForKey:DEF_DELAY]]];
    [tableView reloadData];
    [tableView scrollRowToVisible:[tableView numberOfRows]-1];

    // status field info:
    [theDirectory release];
    theDirectory = [[[aFile stringByDeletingLastPathComponent]stringByDeletingPathExtension] copy];

    [statusField setStringValue:
        [NSString stringWithFormat:@"%@: %@",DIRECTORY_KEY, theDirectory]];
    return 1;
}



#define HTML_CODE    @"<HTML><HEAD><title>%@</title></HEAD><BODY BGCOLOR=\"#eeeeff\"><h2>%@</h2><HR><IMG SRC=\"%@\"></BODY></HTML>"

- (void)createAndOpenHTML:(NSString *)outFile
{
    NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
    NSString *htmlFile = [[outFile stringByDeletingPathExtension]stringByAppendingPathExtension:@"html"];
    NSString *s = [NSString stringWithFormat:HTML_CODE,outFile,outFile,[outFile lastPathComponent]];
    [[NSFileManager defaultManager] removeFileAtPath:htmlFile handler:nil]; // get rid of old
    if ([s writeToFile:htmlFile atomically:NO] && [ud boolForKey:DEF_USE_BROWSER])
        [[NSWorkspace sharedWorkspace] openFile:htmlFile];
}


//
// A more complicated, interrelated set of UI items: a Text field & a radio matrix
// Therefore, we must talk to these through a higher level abstraction:
//

- (int)loopKeyValue
{
    switch ( [loopMatrix selectedTag]) {
        case LOOP_NONE: return 0;
        case LOOP_SPECIFIED: return [loopField intValue];
        case LOOP_FOREVER:
        default:
            return -1;
    }
}

- (void)setLoopFromKeyValue:(int)value
{
    switch ( value) {
        case 0: [loopMatrix selectCellWithTag:LOOP_NONE]; break;
        case -1: [loopMatrix selectCellWithTag:LOOP_FOREVER]; break;
        default:
            [loopMatrix selectCellWithTag:LOOP_SPECIFIED];
         [loopField setIntValue:value];
}
}
// *************

- (NSArray *)getLoopArray
{
    switch ( [loopMatrix selectedTag]) {
        case LOOP_NONE:
            return nil;
    
        case LOOP_SPECIFIED:
            return [NSArray arrayWithObjects:@"-loop",
            [NSString stringWithFormat:@"%d",[loopField intValue]],nil];
        case LOOP_FOREVER:
        default:
return [NSArray arrayWithObject:@"-loop"];
    }
    return nil;
}

//
// IB METHODS:
//



- (NSArray *)getFileNames
{
    int count = [gifFileArray count];
    NSMutableArray *a = [NSMutableArray arrayWithCapacity:count*2];
int i;
    [a addObject:[[gifFileArray objectAtIndex:0]path]];
for (i = 1; i<[gifFileArray count]; i++) {
GifInfo *gi = [gifFileArray objectAtIndex:i];
        [a addObject:[NSString stringWithFormat:@"-time %d", [gi delay]]];
        [a addObject:[gi path]];
}
return a;
}

- (void)mergeGifs:(id)sender
{
int count = [gifFileArray count];
    if (count > 0 ) {
        NSString *baseName = (theDirectory && ![theDirectory isEqualToString:@""])?
            [theDirectory lastPathComponent] : @"GIFfun";
        NSString *disp = [[disposalMatrix selectedCell]title];
        NSArray *loop = [self getLoopArray];
        NSString *colorIndexString = [colorIndexField stringValue];
        NSArray *fileNames = [self getFileNames];
        NSMutableArray *args = [NSMutableArray arrayWithCapacity:count*2];
        NSString *outFile;
// get our outputFile:
        [outputFile autorelease];
        outputFile = [[self nextUniqueNameUsing:[[self scratchFolder] stringByAppendingPathComponent:[baseName stringByAppendingPathExtension:@"gif"]]]retain];
outFile = [outputFile lastPathComponent];
// get rid of old file if it exists:
[[NSFileManager defaultManager] removeFileAtPath:outputFile handler:nil];


/// grab the options and arguments:

if (colorIndexString && ![colorIndexString isEqualToString:@""])
[args addObject:[NSString stringWithFormat:@"-trans %d",[colorIndexField intValue]]];
        [args addObject:[NSString stringWithFormat:@"-time %d",[[gifFileArray objectAtIndex:0]delay]]];

[args addObject:@"-o"];
[args addObject:outFile];
if (loop) [args addObjectsFromArray:loop];
[args addObject:@"-disp"];
[args addObject:disp];
[args addObjectsFromArray:fileNames];

// Here's my groovy Task wrapper which logs errors:

        [[SDLogTask alloc]initAndLaunchWithArgs:args executable:whirlgif
            directory:[outputFile stringByDeletingLastPathComponent]
             logToText:logText includeStandardOutput:NO owner:self];

    } else {
        NSBeep();
        [statusField setStringValue:DROP_ON_KEY];
    }
}

//
// Here is the callback from SDLogText
//
- (void)taskTerminated:(BOOL)success
{
    if(success) {
        [ouputDragWell setFile:outputFile];
        [statusField setStringValue:[NSString stringWithFormat:@"%@: %@",FINISHED_MSG,outputFile]];
        [self createAndOpenHTML:outputFile];
    } else [statusField setStringValue:JOB_FAILED];
}

///*********

- (void)openGifFolder:(id)sender
{
    // run open panel to allow selection of a directory
    id openpanel = [NSOpenPanel openPanel];

    [openpanel setTitle:@"Open Directory of GIFS"];
    [openpanel setAllowsMultipleSelection:NO];
    [openpanel setCanChooseDirectories:YES];
    if ([openpanel runModal] == NSOKButton) {
        [self loadGifsInDirectory:[openpanel filename]];
    }
}

- (void)addAnotherFile:(id)sender
{
    NSArray *fileTypes;
    id openpanel = [NSOpenPanel openPanel];

#if defined(OS_API)
    fileTypes = [NSArray arrayWithObjects:@"gif",@"GIF",nil];
    [openpanel setTitle:@"Add GIF Files..."];
#else
    fileTypes = [NSImage imageFileTypes];
    [openpanel setTitle:@"Add Image Files..."];

#endif

    [openpanel setAllowsMultipleSelection:YES];

    if ([openpanel runModalForTypes:fileTypes]) {
        int index = 0;
        NSString *directory = (NSString *)[openpanel directory] ;
        NSArray *files = [openpanel filenames];
        int numFiles = [files count];
        while (index < numFiles) {
        /* add file to menu */
            NSString *fullName = [directory stringByAppendingPathComponent:[files objectAtIndex:index]];
            if ([self isGifFile:fullName]) [self addGifFile:fullName];
                index++;
        }
    }
}


- (void)clearGifs:(id)sender;
{
    [gifFileArray autorelease];
    gifFileArray = [[NSMutableArray alloc]initWithCapacity:1];
    [statusField setStringValue:@""];
    [ouputDragWell setFile:nil];
    [tableView reloadData];
}


- (void)changeLoop:(id)sender;
{
    if (sender == loopField) {
        [loopMatrix selectCellWithTag:LOOP_SPECIFIED];
    } else if (sender == loopMatrix) {    // it's the matrix
        [loopField setEnabled: ([sender selectedTag] == LOOP_SPECIFIED)];
        if ([sender selectedTag] == LOOP_SPECIFIED) [loopField selectText:self];
    }
}

//
// saving
//

// these are the NSFileManger callbacks:
+ (BOOL)fileManager:(NSFileManager *)manager
shouldProceedAfterError:(NSDictionary *)errorDict
{
    int result;
    result = NSRunAlertPanel(@"GIFfun",
        @"File operation error: %@ with file: %@",@"Proceed", @"Stop", NULL,
        [errorDict objectForKey:@"Error"],
        [errorDict objectForKey:@"Path"]);

    if (result == NSAlertDefaultReturn)
        return YES;
    else
        return NO;
}

+ (void)fileManager:(NSFileManager *)fm willProcessPath:(NSString *)path
{
}

//
// Save the animated GIF somewhere other than in /tmp!
//

- (void)saveAs:(id)sender;
{
    if (outputFile && ![outputFile isEqual:@""]) {
        static NSSavePanel *sp = nil;
        if (!sp) {
            sp = [[NSSavePanel savePanel]retain];
            [sp setTitle:SAVE_AS];
            [sp setDirectory:NSHomeDirectory()];
        }
        if ([sp runModalForDirectory:nil file:[outputFile lastPathComponent]]) {
            [[NSFileManager defaultManager] copyPath:outputFile
                toPath:[sp filename] handler:[self class]];
        }
    } else NSBeep();
}

//

// Save the collected GIFs and settings in a easy-to-read way
//

// define some keys which could be defined in headers for others...
#define NUM_OTHER_DICT_ITEMS    6
#define DICT_SIZE    (2* [gifFileArray count] + NUM_OTHER_DICT_ITEMS)

#define KEY_LOOP    @"Number_Of_Loops"
#define KEY_DISPOSAL    @"Disposal_Method"
#define KEY_TRANSPARENT    @"Transparent_Index"

- (NSString *)pathKeyForGifInfo:(GifInfo *)gi
{
// this is too strict and not very general
// for example if we do a similar animation, but different root
// name, then are settings won't work!
// instead, we'll optimize for Create's numbering system
// and basically, write the numeric identifier of the frame
// and we'll overlook the actual path
// return [[gi path] lastPathComponent];
    NSString *path = [[gi path] lastPathComponent];
// get rid of "gif":
    NSString *justNum = [path stringByDeletingPathExtension];
// now grab number: ie: 00 or 01
    NSString *numIdentifier = [justNum pathExtension];
    if (!numIdentifier || [numIdentifier isEqualToString:@""]) return path;
    return numIdentifier;
}

- (NSString *)ordinalKeyForGifInfo:(GifInfo *)gi
{
    return [NSString stringWithFormat:@"%@-order",[self pathKeyForGifInfo:gi]];
}

- (void)reorderGifArrayWithDict:(NSDictionary *)dict
{
    int i,j , cnt = [gifFileArray count];
    NSMutableArray *newList = [[NSMutableArray allocWithZone:[self zone]] initWithCapacity:cnt];
    for (i = 0; i < cnt; i++) {
        for (j = 0; j < cnt; j++) {
            GifInfo *gi = [gifFileArray objectAtIndex:j];
            id number;
            if ((number = [dict objectForKey:
                [self ordinalKeyForGifInfo:gi]]) != nil) {
        if ([number intValue] == i) {
                    [newList addObject:gi];
                    break;
                }
            }
        }
    }

    if ([newList count] == cnt) {
        [gifFileArray autorelease];
        gifFileArray = newList;
        [tableView reloadData];
    } else [newList autorelease];
}


- (void)writeSettingsToDict:(NSMutableDictionary *)dict
{
    int i;
// write the delays between frames:
// and the position of the GIF in the animation
// Note that we only write out strings, arrays, data or dicts in order that
// the data can be serialized...

    for (i = 0; i < [gifFileArray count]; i++) {
        GifInfo *gi = [gifFileArray objectAtIndex:i];
        [dict setObject:[NSString stringWithFormat:@"%d",
            [gi delay]] forKey:[self pathKeyForGifInfo:gi]];
        [dict setObject:[NSString stringWithFormat:@"%d", i]
            forKey:[self ordinalKeyForGifInfo:gi]];
    }
    [dict setObject:[NSString stringWithFormat:@"%d",[self loopKeyValue]]
            forKey:KEY_LOOP];
    [dict setObject:[NSString stringWithFormat:@"%d",
            [disposalMatrix selectedTag]] forKey:KEY_DISPOSAL];
    [dict setObject:[colorIndexField stringValue]
            forKey:KEY_TRANSPARENT];
}

- (void)readSettingsFromDict:(NSDictionary *)dict
{
    int i;
    BOOL needToReorder = NO;
// Are the GIFS in the correct order?
// Each GIFs ordinality key should match its position...
// but we are lazy, and we'll do this only if necessary

// read the delays between frames:
    for (i = 0; i < [gifFileArray count]; i++) {
        GifInfo *gi = [gifFileArray objectAtIndex:i];
        id number;
        if ((number = [dict objectForKey:
            [self pathKeyForGifInfo:gi]]) != nil)
            [gi setDelay:[number intValue]];
        if ((number = [dict objectForKey:
            [self ordinalKeyForGifInfo:gi]]) != nil)
            if ([number intValue] != i) needToReorder = YES;
    }


    [self setLoopFromKeyValue:[[dict objectForKey:KEY_LOOP]intValue]];
    [disposalMatrix selectCellWithTag:[[dict objectForKey:KEY_DISPOSAL]intValue]];
    [colorIndexField setStringValue:[dict objectForKey:KEY_TRANSPARENT]];

// OK - do the reordering work if you have to...
    if (needToReorder) [self reorderGifArrayWithDict:dict];

}

//
// save settings
//

- (BOOL)saveDictToFile:(NSString *)file
{
    NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithCapacity:DICT_SIZE];
// write a dictionary containing our settings to this directory
    [self writeSettingsToDict:dict];
    return [dict writeToFile:file atomically:NO];
}

- (void)saveSettings:(id)sender;
{
    if ([gifFileArray count]) {
        static NSSavePanel *sp = nil;
        NSString *dir = [[[gifFileArray objectAtIndex:0] path]
            stringByDeletingLastPathComponent];
        if (!sp) {
            // retain it, or you'll crash on the second invoke!
            sp = [[NSSavePanel savePanel]retain];
            [sp setTitle:SAVE_GIF_SETTINGS];
            [sp setDirectory:NSHomeDirectory()];
            [sp setRequiredFileType:GIFFUN_SETTINGS_FILETYPE];
        }
// we'll use the last directory selected
// but we'll use the basis of the file name to be
// the original name that was loaded:
        if ([sp runModalForDirectory:dir file:GIFFUN_SETTINGS_FILE]) {
            [self saveDictToFile:[sp filename]];
        }
    }
}


- (BOOL)writeGIFSToPath:(NSString *)destPath
{
    // Create the destination directory
    if (directoryOK(destPath)) {
        int i;
    // for each gif info:
    // copy to the destination directory
    // but only if it doesn't exist already!
        for (i = 0; i < [gifFileArray count]; i++) {
            GifInfo *gi = [gifFileArray objectAtIndex:i];
            NSString *gifName = [[gi path] lastPathComponent];
            NSFileManager *fm = [NSFileManager defaultManager];
            NSString *destFile = [destPath stringByAppendingPathComponent:gifName];
            if (![fm fileExistsAtPath:destFile])
                [fm copyPath:[gi path]
                    toPath:destFile
                    handler:[self class]];    
        }
        return [self saveDictToFile:
         [destPath stringByAppendingPathComponent:GIFFUN_SETTINGS_FILE]];
    }
    return NO;
}

- (void)saveGifAnimDirectory:(id)sender;
{
    if ([gifFileArray count]) {
        static NSSavePanel *sp = nil;
        NSString *filename = [[[[gifFileArray objectAtIndex:0] path]
            stringByDeletingLastPathComponent] lastPathComponent];
        if (!sp) {
            sp = [[NSSavePanel savePanel]retain];
            [sp setTitle:SAVE_GIF_ANIMS];
            [sp setDirectory:NSHomeDirectory()];
            [sp setRequiredFileType:GIFFUN_FILE_TYPE];
        }
// we'll use the last directory selected
    // but we'll use the basis of the file name to be
    // the original name that was loaded:
        if ([sp runModalForDirectory:nil file:filename]) {
            [self writeGIFSToPath:[sp filename]];
        }
    }
}

//
// Covers for other nib files actions:
//

- (void)runDefaultsPanel:(id)sender;
{
    [[GIFPrefs sharedDefaults] orderFront:self];
}

- (void)loadHelp:(id)sender
{
    [[InfoAndHelp sharedHelp] orderFrontHelp:self];
}

- (void)loadWhirlGifHelp:(id)sender;
{
    [[InfoAndHelp sharedHelp] orderFrontWhirlHelp:self];
}

- (void)orderFrontInfo:(id)sender;
{
    [[InfoAndHelp sharedHelp] orderFrontInfo:self];
}

- (void)loadSample:(id)sender;
{
    NSString *path = [[NSBundle mainBundle] pathForResource:SAMPLE_GIFS ofType:GIFFUN_FILE_TYPE];
    [self loadGifsInDirectory:path];
}

//
// Setting up the TableView the way we want it:
//

#define ROW_HEIGHT 48

- (void)setLookAndFeelOfTableView
{
    NSImageCell *ic = [[NSImageCell allocWithZone:[self zone]] initImageCell:nil];
    NSTableColumn *tc = [tableView tableColumnWithIdentifier:I_IMAGE];

// Make the rows taller so we can see our GIFS:
    [tableView setRowHeight:ROW_HEIGHT];

// We want the GIF not to be stretched, and we set the TableColumn's cell to our new ImageCell:
    [ic setImageScaling:NSScaleProportionally]; // or NSScaleToFit
    [tc setDataCell:ic];

// the Delay field is the only editable one
// We'll show that with a bigger font and a bezeled field:

    tc = [tableView tableColumnWithIdentifier:I_DELAY];
    [[tc dataCell] setFont:[NSFont userFontOfSize:18.]];
    [[tc dataCell] setBezeled:YES];
}


//
// TableView delegate methods:
//

- (int)numberOfRowsInTableView:(NSTableView *)tv
{
    return [gifFileArray count];
}

- (NSString *)uiPath:(GifInfo *)gi
{
    return [[[gi path]lastPathComponent]stringByDeletingPathExtension];
}

- tableView:(NSTableView *)tv
objectValueForTableColumn:(NSTableColumn *)tc
row:(int)row
{
    NSString *ident = [tc identifier];
    if (tv == tableView) {
    if ([ident isEqual:I_NUMBER]) return [NSNumber numberWithInt:row + 1];
    else if ([ident isEqual:I_IMAGE])
        return [[gifFileArray objectAtIndex:row]image];
    else if ([ident isEqual:I_PATH])
        return [self uiPath:[gifFileArray objectAtIndex:row]];
    else if ([ident isEqual:I_DELAY])
        return [NSNumber numberWithInt:[[gifFileArray objectAtIndex:row]delay]];
    }
    return @"";
}

- (void)tableView:(NSTableView *)tv setObjectValue:(id)object forTableColumn:(NSTableColumn *)tc row:(int)row;
{
    NSString *ident = [tc identifier];
    if (tv == tableView) {
        if ([ident isEqual:I_DELAY])
            [[gifFileArray objectAtIndex:row]setDelay:[object intValue]];
    }
}

//
// SDMovingRowsProtocol for reordering of rows:
//
- (BOOL)rowOK:(int)row
{
    return (row > -1 && row < [gifFileArray count]);
}

- (unsigned int)dragReorderingMask:(int)col;
{
    // DRAG_ALWAYS means do it with no modifier needed at all
    if (col == IMAGE_COLUMN_NUMBER)     return DRAG_ALWAYS;
    else return NSCommandKeyMask;
}

- (void)willMoveRow:(int)row;
{
    [statusField setStringValue:@""];
}

- (NSImage *)imageForRow:(int)row;
{
    if ([self rowOK:row]) {
        GifInfo *gi = [gifFileArray objectAtIndex:row];
        [statusField setStringValue:
            [NSString stringWithFormat:@"%@: %@", NOW_MOVING_MSG,
             [self uiPath:gi]]];
        return [gi image];
    }
    return nil;
}

- (BOOL)tableView:(NSTableView *)tv didDepositRow:(int)rowToMove at:(int)newPosition
{
    if (rowToMove != -1 && newPosition != -1) {
        id object = [gifFileArray objectAtIndex:rowToMove];
        if (newPosition < [gifFileArray count] - 1) {
            [gifFileArray removeObjectAtIndex:rowToMove];
            [gifFileArray insertObject:object atIndex:newPosition];
        } else {
            [gifFileArray removeObjectAtIndex:rowToMove];
            [gifFileArray addObject:object];
        }
        [statusField setStringValue:[NSString stringWithFormat:
            @"%@ %@ %@ %d %@ %d", MOVED_MSG,[self uiPath:object],
            FROM_MSG,rowToMove + 1,DEST_MSG,newPosition + 1]];
        return YES;    // ie reload
    }
    return NO;
}

// cut copy paste
- (void)copy:sender
{
    int toCopy = [tableView selectedRow];
    if ([self rowOK:toCopy])
        copiedRow = [[gifFileArray objectAtIndex:toCopy]copy];
    else {
        NSBeep();
        [statusField setStringValue:
            [gifFileArray count]>0?SELECT_ROW_KEY:DROP_ON_KEY];
    }
}

- (void)cut:sender
{
    int toCut = [tableView selectedRow];
    if ([self rowOK:toCut]) {
        copiedRow = [[gifFileArray objectAtIndex:toCut]copy];
        [gifFileArray removeObjectAtIndex:toCut];
        [tableView reloadData];
    }else {
        NSBeep();
        [statusField setStringValue:[gifFileArray count]>0?SELECT_ROW_KEY:DROP_ON_KEY];
}

}

- (void)paste:sender
{
    int location = [tableView selectedRow];
    int count = [gifFileArray count];
    if (copiedRow != nil) {
        if (location == -1 || location >= count - 1)
            [gifFileArray addObject:[copiedRow copy]];
        else
            [gifFileArray insertObject:[copiedRow copy] atIndex:location+1];
        [tableView reloadData];
    } else {
        NSBeep();
        [statusField setStringValue:
            [gifFileArray count]>0?SELECT_ROW_KEY:DROP_ON_KEY];
    }
}


@end
TOC | PREV | NEXT
Created by Stone Design's Create on 4/30/1998
©2000 Stone Design top