/* Copyright (c) 2004, Noah Sorscher
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 	¥ 	Redistributions of source code must retain the above copyright
 * 	notice, this list of conditions and the following disclaimer.
 * 	¥ 	Redistributions in binary form must reproduce the above copyright
 * 	notice, this list of conditions and the following disclaimer in the
 * 	documentation and/or other materials provided with the distribution.
 * 	¥ 	Neither the name of the Noah Sorscher nor the names of its
 * 	contributors may be used to endorse or promote products derived from
 * 	this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */
#import "MHSprite.h"
#import "QTImage.h"
#define TRACK_DURATION 1000
#define DEBUG NO

@interface NSOutlineView (spriteStuff)

- (id) selectedItem;

@end

@implementation NSOutlineView (spriteStuff)

- (id) selectedItem
{
    int index = [self selectedRow];
    if (index == -1) return nil;
    return [self itemAtRow:index];
}

@end

@interface NSMutableArray (duh)
- (void) addObjectsFromArrayIf:(NSArray *)a prefix:(NSString *)pref suffix:(NSString *)suf;
- (void) addObjectsFromArrayIf:(NSArray *)a prefix:(NSString *)pref;
@end

@implementation NSMutableArray (duh)

- (void) addObjectsFromArrayIf:(NSArray *)a
{
    int i, n = [a count];
    for (i=0;i<n;i++) {
        if (![self containsObject:[a objectAtIndex:i]])
            [self addObject:[a objectAtIndex:i]];
    }
}

- (void) addObjectsFromArrayIf:(NSArray *)a prefix:(NSString *)pref suffix:(NSString *)suf
{
    int i, n = [a count];
    for (i=0;i<n;i++) {
        NSString *s = [a objectAtIndex:i];
        if (suf) s = [s stringByAppendingString:@")"];
        if ([s hasCaseInsensitivePrefix:pref] && ![self containsObject:s]) {
            [self addObject:s];
        }
    }
}

- (void) addObjectsFromArrayIf:(NSArray *)a prefix:(NSString *)pref
{
    [self addObjectsFromArrayIf:a prefix:pref suffix:nil];
}

@end

@implementation MHSprite

BOOL isAwake = NO;
id mhSpriteNibInstance = nil;

- (void)windowWillClose:(NSNotification *)aNotification
{
    [spriteMovieView stop:self];
    [theOutline autosaveNow];
}

- (NSRect)windowWillUseStandardFrame:(NSWindow *)sender defaultFrame:(NSRect)defaultFrame
{
    return [self rightSize];
}

- (void)flagsChanged:(NSNotification *)note
{
    NSEvent *event = [NSApp currentEvent];
    unsigned int flags = [event modifierFlags];
    NSToolbarItem *item = [[[spriteWindow toolbar] items] objectAtIndex:2];
    if (flags & NSAlternateKeyMask) {
        [item setAction:@selector(duplicateIncrementing:)];
        [item setLabel:@"Duplicate (Incrementing)"];
    } else {
        [item setAction:@selector(duplicate:)];
        [item setLabel:@"Duplicate"];
    }
}

BOOL backspace = NO;

- (void) backSpace
{
    backspace = YES;
}

- (NSArray *)textView:(NSTextView *)tview completions:(NSArray *)words forPartialWordRange:(NSRange)charRange indexOfSelectedItem:(int *)index
{
	*index = -1;
	NSDictionary *actions = [NSDictionary dictionaryWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"SpriteActions" ofType:@"plist"]];
	NSArray *keys = [actions allKeys];
	NSMutableArray *list = [NSMutableArray array];
	// find the line and word
	NSRange r = [[tview string] lineRangeForRange:charRange];
	if (r.location == NSNotFound) return nil;
	NSString *line = [[tview string] substringWithRange:r];
        r.location = charRange.location - r.location;
        r.length = charRange.length;
	NSString *word = [line substringWithRange:r];
        
        int i, n;
        // complete the possible correct commands
        if (r.location > 1) { // there's a previous clause
            line = [line substringToIndex:r.location];
            if ([line hasSuffix:@"."]) line = [line substringToIndex:[line length]-1];
            r = [line rangeOfString:@"." options:NSBackwardsSearch];
            if (r.length) line = [line substringFromIndex:r.location+1];
            // first check if it's one we can complete args to
            if ([line hasSuffix:@"("]) {
                NSArray *cmds = [NSArray arrayWithObjects:@"GoToChapter(", @"TrackNamed(", @"SetLanguage(", nil];
                n = [cmds count];
                for (i=0;i<n;i++) {
                    NSString *cmd = [cmds objectAtIndex:i];
                    if ([line hasCaseInsensitivePrefix:cmd]) {
                        //word = [word substringFromIndex:[cmd length]];
                        NSArray *args = nil;
                        switch (i) {
                            case 0: args = [[(MHMovie *)[spriteMovieView movie] chapters] chapterNames]; break;
                            case 1: args = [self getTracksOfType:0]; break;
                            case 2: args = [tLanguages stringLangs]; break;
                            default: break;
                        }
                        [list addObjectsFromArrayIf:args prefix:word suffix:@")"];
                        return list;
                    }
                }
            }
            n = [keys count];
            for (i=0;i<n;i++) {
                NSString *key = [keys objectAtIndex:i];
                if ([line hasCaseInsensitivePrefix:key]) {
                    NSArray *values = [actions objectForKey:key];
                    if ([values containsObject:@"*"])
                        [list addObjectsFromArrayIf:keys prefix:word];
                    else
                        [list addObjectsFromArrayIf:values prefix:word];
                }
            }
        } else { // it's the first clause of the line
            [list addObjectsFromArrayIf:keys prefix:word];
        }
        
	return list;
}

- (void) autoComplete
{
    NSLog(@"autocomplete");
    if (backspace) {
        backspace = NO;
        return;
    }
    NSString *oldString = [qScriptBox string];
    if (![oldString length]) return;
    NSRange r = [oldString rangeOfString:@"." options:NSBackwardsSearch];
    int dotLocation = 0;
	if (r.length) dotLocation = r.location;
	r = [oldString rangeOfString:@"\n" options:NSBackwardsSearch];
	if (r.length && r.location > dotLocation) dotLocation = r.location;
    if (dotLocation > [oldString length]-2) return;
    if (dotLocation) {
        dotLocation++;
        oldString = [oldString substringFromIndex:dotLocation];
    }
    NSArray *possibilities = [NSArray arrayWithObjects:@"TrackNamed(", @"GoToChapter(", @"SetEnabled(", @"SetMovieLanguage(", @"TrackAtIndex(", @"GoToPage(", @"GoToTime(", @"SetRate(", @"SetVolume(", @"OnMouseEnter", @"OnMouseExit", @"OnFrameLoad", @"StartPlaying", @"StepForward", @"StepBackward", @"GoToBeginning", @"GoToEnd", @"SaveTime", @"GoToSavedTime", nil];
    
    NSString *ext = [oldString extensionLessSharedPrefix:possibilities];
    if ([ext length]) { // complete a command
        oldString = [qScriptBox string];
        r.location = [oldString length];
        r.length = [ext length];
        [qScriptBox setString:[oldString stringByAppendingString:ext]];
        [qScriptBox setSelectedRange:r];
    } else { // a complete command
        int i, n = 4;
        for (i=0;i<n;i++) {
            NSString *pos = [possibilities objectAtIndex:i];
            if ([oldString hasCaseInsensitivePrefix:pos]) {
                NSArray *args = nil;
                switch (i) {
                    case 0: args = [self getTracksOfType:0]; break;
                    case 1: args = [[(MHMovie *)[spriteMovieView movie] chapters] chapterNames]; break;
                    case 2: args = [NSArray arrayWithObjects:@"yes", @"no", @"true", @"false", nil]; break;
                    case 3: args = [tLanguages stringLangs]; break;
                    default: break;
                }
                if (args) {
                    oldString = [oldString substringFromIndex:[pos length]];
                    ext = [oldString extensionLessSharedPrefix:args];
                    if ([ext length]) {
                        oldString = [qScriptBox string];
                        r.location = [oldString length];
                        r.length = [ext length] + 1;
                        [qScriptBox setString:[NSString stringWithFormat:@"%@%@)",oldString, ext]];
                        [qScriptBox setSelectedRange:r];
                    }
                }
            }
        }
    }
}

- (void)textDidChange:(NSNotification *)aNotification
{
    //[self autoComplete];
    [NSObject cancelPreviousPerformRequestsWithTarget:qScriptBox];
    [qScriptBox performSelector:@selector(complete:) withObject:self afterDelay:3];
    NSMutableDictionary *item = [theOutline selectedItem];
    if (!item) return;
    NSString *string = [[qScriptBox string] copy];
    if (string)
        [item setObject:string forKey:@"Action"];
    [qScriptBox setTextColor:[NSColor blackColor]];
}

+ (id)nibInstance
{
    return mhSpriteNibInstance;
}

- (void)postUpdate:(NSString *)string done:(double)done
{
    if (string) {
        [progText setStringValue:string];
        [progBar setDoubleValue:done];
    }
}

+ (id)goWithMovie:(NSMovie *)aMovie
{
    if (!isAwake) {
        NSWindowController *winCon = [[NSWindowController alloc] initWithWindowNibName:@"SpriteEditor"];
        [winCon showWindow:self];
    }
    //aMovie = [[NSMovie alloc] initWithURL:[NSURL fileURLWithPath:@"/mov.mov"] byReference:NO];
    [mhSpriteNibInstance setMovie:aMovie];
    [mhSpriteNibInstance open];
    return mhSpriteNibInstance;
}

- (void)populateMenu:(NSMenu *)aMenu
{
    NSArray *titles = [NSArray arrayWithObjects:@"New Sprite", @"New Page", @"Delete Selection", @"Duplicate Selection", @"Duplicate (Incrementing)", @"Wizard", @"Update Movie", @"Advanced Options", @"Quick Sprite Mode", nil];
    void *actions[] = { @selector(newSprite:), @selector(newPage:), @selector(deletePage:), @selector(duplicate:), @selector(duplicateIncrementing:), @selector(wizard:), @selector(updateMovie:), @selector(more:), @selector(quickMode:)  };
    int i, n = [titles count];
    for (i=0;i<n;i++) {
	NSMenuItem *item = [aMenu addItemWithTitle:[titles objectAtIndex:i] action:actions[i] keyEquivalent:@""];
	[item setTarget:self];
	if (i == 8) // quickMode
	    quickModeMenu = item;
    }
}

+ (void)setMovie:(NSMovie *)aMovie
{
    [mhSpriteNibInstance setMovie:aMovie];
}

+ (void)setX:(int)x Y:(int)y
{
    [mhSpriteNibInstance iSetX:x Y:y];
}

+ (NSColor *)bgColor
{
    return [mhSpriteNibInstance color];
}

+ (NSSize)naturalSize
{
    return [mhSpriteNibInstance size];
}

+ (id)selectedItem		{	return [mhSpriteNibInstance selectedTableItem];	}
+ (int)selectedImage            {       return [mhSpriteNibInstance selectedImage];     }
+ (void)selectItem:(id)item	{	[mhSpriteNibInstance selectItem:item];		}
+ (void)reloadOutline		{	[mhSpriteNibInstance reload];			}
+ (BOOL)qsMode			{       return [mhSpriteNibInstance qsMode];		}
+ (void)newSpriteWithRect:(NSRect)rect  { [mhSpriteNibInstance newSpriteWithRect:rect]; }
- (BOOL)qsMode			{       return [quickModeMenu state];			}
- (void)reload			{	[theOutline reloadData];			}
+ (NSMenu *)rightClickMenu {	return [mhSpriteNibInstance rightClickMenu];	}

- (NSMenu *)rightClickMenu
{
	NSMutableDictionary *theHit = [self selectedTableItem];
	if (!theHit) return nil;
	NSMenu *menu = [[[NSMenu alloc] init] autorelease];
    // action
    NSArray *actions = [NSArray arrayWithObjects:@"None", @"Go To Chapter", @"Set Audio Track", @"Set Text Track", @"Step Forward", @"Step Backward", @"Go To Page", nil];
    NSArray *keys = [NSArray arrayWithObjects: @"none", @"GoToChapter(", @"// set audio track", @"// set text track", @"stepforward", @"stepbackward", @"gotopage(", nil];
    NSString *action = [theHit objectForKey:@"Action"];
    if (!action) action = @"";
    NSScanner *scan = [NSScanner scannerWithString:action];
    [scan setCaseSensitive:NO];
    int i, n = [actions count];
    for (i=0;i<n;i++) {
	NSMenuItem *anItem = [[[NSMenuItem alloc] initWithTitle:[actions objectAtIndex:i] action:@selector(setAction:) keyEquivalent:@""] autorelease];
	[anItem setTarget:self];
        if (i==0 && ![action length]) [anItem setState:YES];
        else if ([scan scanString:[keys objectAtIndex:i] intoString:nil])
            [anItem setState:YES];
	[menu addItem:anItem];
        // values
        NSArray *values = nil;
        switch (i) {
            case 1: values = [self chapterNames]; break;
            case 2: values = [self getTracksOfType:SoundMediaType]; break;
            case 3: values = [self getTracksOfType:TextMediaType]; break;
            case 6: values = [self pageNumbers]; break;
            default: break;
        }
        if (values) {
            NSString *value = nil;
            int x = -1, j, m = [values count];
            if ([anItem state]) { // if this is our action type
                switch (i) {
                    case 1: [scan scanUpToString:@")" intoString:&value]; break;
                    case 2: case 3:
                        while (![scan isAtEnd]) {
                            [scan scanPastString:@"TrackNamed("];
                            [scan scanUpToString:@")" intoString:&value];
                            if ([scan scanString:@".SetEnabled(" intoString:nil] && ([scan scanString:@"YES" intoString:nil] || [scan scanString:@"TRUE" intoString:nil]))
                                break;
                            else value = nil;
                        } break;
                    case 6:
                        [scan scanInt:&x];
                        if (x >=0 && x < m) value = [NSString stringWithFormat:@"%d", x];
                        break;
                    default: break;
                }
            }
            NSMenu *vMenu = [[[NSMenu alloc] init] autorelease];
            [anItem setSubmenu:vMenu];
            for (j=0;j<m;j++) {
                NSString *aValue = [values objectAtIndex:j];
                NSMenuItem *vItem = [[[NSMenuItem alloc] initWithTitle:aValue action:@selector(setValue:) keyEquivalent:@""] autorelease];
                if (i == 6) // go to page
                    aValue = [NSString stringWithFormat:@"%d", [aValue intValue]];
                if (value && [value isEqualToString:aValue])
                    [vItem setState:YES];
                [vItem setTarget:self];
                [vMenu addItem:vItem];
            }
        }
    }
	return menu;
}

- (void)newSpriteWithRect:(NSRect)rect
{
    if (rect.size.width < 1 || rect.size.height < 1) return;
    NSMutableDictionary *menu = [[spriteMovieView movie] menu];
    if (!menu)
        menu = [self newMenu];
    NSMutableDictionary *track = nil;
    int index = [theOutline selectedRow];
    if (index != -1) {
        NSMutableDictionary *item = [theOutline itemAtRow:index];
        if (![[item objectForKey:@"isLeaf"] boolValue])
            track = item;
        else
            track = [item objectForKey:@"parent"];
    } else {
        NSArray *tracks = [menu objectForKey:@"tracks"];
        if ([tracks count])
            track = [tracks objectAtIndex:0];
    }
    if (!track) {
        [self newPage:nil];
        track = [[menu objectForKey:@"tracks"] objectAtIndex:0];
    }
    NSMutableDictionary *sprite = [NSMutableDictionary dictionary];
    [sprite setObject:@"Sprite" forKey:@"Name"];
    [sprite setObject:[NSNumber numberWithBool:YES] forKey:@"isLeaf"];
    [sprite setObject:track forKey:@"parent"];
    [[track objectForKey:@"sprites"] addObject:sprite];
    [sprite setObject:[NSString stringWithFormat:@"%d", (int)rect.origin.x] forKey:@"X"];
    [sprite setObject:[NSString stringWithFormat:@"%d", (int)rect.origin.y] forKey:@"Y"];
    [sprite setObject:[NSString stringWithFormat:@"%d", (int)rect.size.width] forKey:@"Width"];
    [sprite setObject:[NSString stringWithFormat:@"%d", (int)rect.size.height] forKey:@"Height"];
    [theOutline reloadData];
    [theOutline expandItem:track];
    [theOutline selectRow:[theOutline rowForItem:sprite] byExtendingSelection:NO];
    [theImageView setNeedsDisplay:YES];
}

- (void)selectItem:(id)item
{
    int row = [theOutline rowForItem:item];
    if (row == -1) {
        id parent = [item objectForKey:@"parent"];
        if (parent) {
            [theOutline expandItem:parent];
            row = [theOutline rowForItem:item];
        }
    }
    if (row != -1)
        [theOutline selectRow:row byExtendingSelection:NO];
}

- (id)selectedTableItem
{
    if ([theOutline selectedRow] == -1) return nil;
    return [theOutline itemAtRow:[theOutline selectedRow]];
}

- (int)selectedImage
{   return [imageSelect indexOfSelectedItem];   }

- (NSSize)size
{
    return naturalSize;
}

- (NSColor *)color
{
    return [bgColor color];
}

- (IBAction)setColor:(id)sender
{
    NSColor *color = [bgColor color];
    NSMutableDictionary *item = [theOutline itemAtRow:[theOutline selectedRow]];
    if (!item)
        item = [theOutline itemAtRow:0];
    if (!item)
        return;
    if ([[item objectForKey:@"isLeaf"] boolValue])
        item = [item objectForKey:@"parent"];
    NSData *dColor = [NSArchiver archivedDataWithRootObject:color];
    [item setObject:dColor forKey:@"color"];
    [[NSUserDefaults standardUserDefaults] setObject:dColor forKey:@"spriteBGColor"];
    [theImageView setNeedsDisplay:YES];
    [[NSColor blackColor] set];
}

- (NSImage *)imageForSprite:(NSMutableDictionary *)sprite key:(NSString *)key
{
    TimeValue time = 0;
    NSString *path = [sprite objectForKey:key];
    if (path && ![path hasPrefix:@"/"]) {
        time = [path intValue];
        path = [[[spriteMovieView movie] URL] path];
    }
    NSColor *aColor = nil;
    NSData *dColor = [sprite objectForKey:@"keyColor"];
    if (dColor) aColor = [NSUnarchiver unarchiveObjectWithData:dColor];
    NSImage *image = [[QTImage alloc] initWithFile:path transparency:aColor time:time];
    return image;
}

- (IBAction)setImage:(id)sender
{
    NSArray *anArray = [NSArray arrayWithObjects:@"Image", @"Rollover", @"Click", nil];
    NSArray *keys = [NSArray arrayWithObjects:@"image", @"rimage", @"cimage", nil];
    NSMutableDictionary *item = [theOutline itemAtRow:[theOutline selectedRow]];
    if (![item objectForKey:@"isLeaf"]) return;
    int dest = [imageSelect indexOfSelectedItem];
    int type = [imageType selectedRow];
    if (type == 0) { // Movie Frame
        Movie mov = [[spriteMovieView movie] QTMovie];
        TimeValue tv;
        TimeRecord tr;
        tv = GetMovieTime(mov, &tr);
        NSString *key = [NSString stringWithFormat:@"%d", tv];
        [item setObject:key forKey:[anArray objectAtIndex:dest]];
        NSImage *image = [self imageForSprite:item key:[anArray objectAtIndex:dest]];
        if (image)
            [item setObject:image forKey:[keys objectAtIndex:dest]];
    } else { // File
        NSOpenPanel *theOpenPanel = [NSOpenPanel openPanel];
        int result;
        NSString *choice;
    
        [theOpenPanel setCanChooseFiles:YES];
        [theOpenPanel setCanChooseDirectories:NO];
        [theOpenPanel setResolvesAliases:YES];
        [theOpenPanel setAllowsMultipleSelection:NO];
        result = [theOpenPanel runModalForDirectory:nil file:nil types:nil];
        if (result == NSOKButton) {
            choice = [[theOpenPanel filenames] objectAtIndex:0];
            [item setObject:choice forKey:[anArray objectAtIndex:dest]];
            NSImage *image = [self imageForSprite:item key:[anArray objectAtIndex:dest]];
            if (image) {
            	[item setObject:image forKey:[keys objectAtIndex:dest]];
            	[item setObject:[NSNumber numberWithInt:[image size].width] forKey:@"Width"];
            	[item setObject:[NSNumber numberWithInt:[image size].height] forKey:@"Height"];
            }
        }
    }
    [theOutline reloadData];
    [theOutline expandItem:[item objectForKey:@"parent"]];
    [theOutline selectRow:[theOutline rowForItem:item] byExtendingSelection:NO];
    [theImageView setNeedsDisplay:YES];
}

- (NSMutableDictionary *)newMenu
{
    MHMovie *movie = (MHMovie *)[spriteMovieView movie];
    if (!movie) return nil;
    NSMutableDictionary *menu = [movie menu];
    if (menu) [menu release];
    menu = [[NSMutableDictionary alloc] init];
    [menu setObject:[NSMutableArray array] forKey:@"tracks"];
    [movie setMenu:menu];
    return menu;
}

- (IBAction)newPage:(id)sender
{
    MHMovie *aMovie = (MHMovie *)[spriteMovieView movie];
    if (nil == aMovie) return;
    NSMutableDictionary *menu = [[spriteMovieView movie] menu];
    if (!menu)
        menu = [self newMenu];
    NSMutableDictionary *track = [[NSMutableDictionary alloc] init];
    //MHSpriteTrack *track = [(MHSpriteTrack *)[MHSpriteTrack alloc] initWithMovie:aMovie];
    //[track setObject:[NSArchiver archivedDataWithRootObject:[bgColor color]] forKey:@"bgColor"];
    [track setObject:@"Page" forKey:@"Name"];
    [track setObject:[NSMutableArray array] forKey:@"sprites"];
    [[menu objectForKey:@"tracks"] addObject:track];
    [theOutline reloadData];
    [theOutline selectRow:[theOutline rowForItem:track] byExtendingSelection:NO];
    [self setColor:nil];
}

- (IBAction)newSprite:(id)sender
{
    NSMutableDictionary *menu = [[spriteMovieView movie] menu];
    if (!menu)
        menu = [self newMenu];
    if (!menu) return;
    NSMutableDictionary *track = nil;
    int index = [theOutline selectedRow];
    if (index != -1) {
        NSMutableDictionary *item = [theOutline itemAtRow:index];
        if (![[item objectForKey:@"isLeaf"] boolValue])
            track = item;
        else
            track = [item objectForKey:@"parent"];
    } else {
        NSArray *tracks = [menu objectForKey:@"tracks"];
        if ([tracks count])
            track = [tracks objectAtIndex:0];
    }
    if (!track) {
        [self newPage:nil];
        track = [[menu objectForKey:@"tracks"] objectAtIndex:0];
    }
    NSMutableDictionary *sprite = [NSMutableDictionary dictionary];
    [sprite setObject:@"Sprite" forKey:@"Name"];
    [sprite setObject:[NSNumber numberWithBool:YES] forKey:@"isLeaf"];
    [sprite setObject:track forKey:@"parent"];
    [[track objectForKey:@"sprites"] addObject:sprite];
    [theOutline reloadData];
    [theOutline expandItem:track];
    [theOutline selectRow:[theOutline rowForItem:sprite] byExtendingSelection:NO];
    [theImageView setNeedsDisplay:YES];
}

- (IBAction)deletePage:(id)sender
{
    [loadLock lock];
    NSMutableDictionary *menu = [[spriteMovieView movie] menu];
    if (!menu)
        menu = [self newMenu];
    id item = [theOutline itemAtRow:[theOutline selectedRow]];
    if (!item) return;
    if (![[item objectForKey:@"isLeaf"] boolValue]) {
        [[menu objectForKey:@"tracks"] removeObject:item];
    } else {
        [[[item objectForKey:@"parent"] objectForKey:@"sprites"] removeObject:item];
    }
    [theOutline reloadData];
    [theImageView setNeedsDisplay:YES];
    [loadLock unlock];
}

- (NSMutableDictionary *)duplicate:(id)sender
{
    NSDictionary *item = [theOutline itemAtRow:[theOutline selectedRow]];
    if (!item) return nil;
    NSMutableDictionary *newItem = [[NSMutableDictionary alloc] init];
    NSArray *keys = [item allKeys];
    NSArray *values = [item allValues];
    int i, n = [keys count];
    if ([[item objectForKey:@"isLeaf"] boolValue]) {
        for (i=0;i<n;i++)
            [newItem setObject:[values objectAtIndex:i] forKey:[keys objectAtIndex:i]];
        [[[item objectForKey:@"parent"] objectForKey:@"sprites"] addObject:newItem];
    } else {
        for (i=0;i<n;i++) {
            NSString *key = [keys objectAtIndex:i];
            if (![key isEqualToString:@"sprites"])
                [newItem setObject:[values objectAtIndex:i] forKey:[keys objectAtIndex:i]];
        }
        NSMutableArray *newSprites = [[NSMutableArray alloc] init];
        [newItem setObject:newSprites forKey:@"sprites"];
        NSArray *oldSprites = [item objectForKey:@"sprites"];
        if (oldSprites) {
            NSArray *skeys, *svalues;
            int j, k;
            n = [oldSprites count];
            for (i=0;i<n;i++) {
                NSDictionary *aSprite = [oldSprites objectAtIndex:i];
                NSMutableDictionary *newSprite = [[NSMutableDictionary alloc] init];
                [newSprites addObject:newSprite];
                skeys = [aSprite allKeys];
                svalues = [aSprite allValues];
                k = [skeys count];
                for (j=0;j<k;j++)
                    [newSprite setObject:[svalues objectAtIndex:j] forKey:[skeys objectAtIndex:j]];
            }
        }
        NSMutableDictionary *menu = [[spriteMovieView movie] menu];
        if (!menu)
            menu = [self newMenu];
        [[menu objectForKey:@"tracks"] addObject:newItem];
    }
    [theOutline reloadData];
    [self selectItem:newItem];
    [theImageView setNeedsDisplay:YES];
    [qScriptBox setTextColor:[NSColor blackColor]];
    return newItem;
}

- (NSString *)nextNameInArray:(NSArray *)names afterName:(NSString *)name
{
    int i, n = [names count];
    NSString *pref = nil;
    for (i=0;i<n;i++) {
        NSString *s = [names objectAtIndex:i];
        if ([s isEqualToString:name]) {
            if (i<n-1) return [names objectAtIndex:i+1];
            else return s;
        }
        if ([s hasPrefix:name]) pref = s;
    }
    return pref;
}

- (IBAction)duplicateIncrementing:(id)sender
{
    NSMutableDictionary *newItem = [self duplicate:nil];
    if ([[newItem objectForKey:@"isLeaf"] boolValue]) {
        NSString *action = [newItem objectForKey:@"Action"];
        NSString *value = nil;
        if (action) {
            NSScanner *scan = [NSScanner scannerWithString:action];
            [scan setCaseSensitive:NO];
            if ([scan scanString:@"gotochapter(" intoString:nil]) {
                [scan scanUpToString:@")" intoString:&value];
                NSArray *chNames = [[(MHMovie *)[spriteMovieView movie] chapters] chapterNames];
                if (chNames && value) {
                    value = [self nextNameInArray:chNames afterName:value];
                    action = [NSString stringWithFormat:@"GoToChapter(%@);", value];
                    [newItem setObject:action forKey:@"Action"];
                    return;
                }
            } else if ([scan scanString:@"// set " intoString:nil]) {
                NSArray *tracks = nil;
                int actionKind = 0;
                if ([scan scanString:@"audio track" intoString:nil]) {
                    tracks = [self getTracksOfType:SoundMediaType];
                    actionKind = 2;
                } else if ([scan scanString:@"text track" intoString:nil]) {
                    tracks = [self getTracksOfType:TextMediaType];
                    actionKind = 3;
                }
                if (tracks) {
                    while (![scan isAtEnd]) {
                        [scan scanPastString:@"tracknamed("];
                        [scan scanUpToString:@")" intoString:&value];
                        [scan scanPastString:@"setenabled("];
                        if ([scan scanString:@"yes" intoString:nil] || [scan scanString:@"true" intoString:nil]) {
                            value = [self nextNameInArray:tracks afterName:value];
                            action = [self stringForAction:actionKind value:value];
                            [newItem setObject:action forKey:@"Action"];
                            return;
                        }
                    }
                }
            } else if ([scan scanString:@"gotopage(" intoString:nil]) {
                int page = -1;
                if ([scan scanInt:&page] && page != -1) {
                    page++;
                    action = [NSString stringWithFormat:@"GoToPage(%d);", page];
                    [newItem setObject:action forKey:@"Action"];
                    return;
                }
            }
        }
    }
}

- (IBAction)updateMovie:(id)sender
{
    MHMovie *movie = (MHMovie *)[spriteMovieView movie];
    if ([movie isOffline]) return;
    [NSThread detachNewThreadSelector:@selector(updateMovieThreaded:) toTarget:self withObject:movie];
}

- (void)updateMovieThreaded:(MHMovie *)movie
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    NSString *plist, *input, *resource;
    input = [[movie URL] path];
    if (![input hasExtension:[NSArray arrayWithObject:@"mov"]]) {
        input = [MHUtil uniquePath:[[MHUtil supportDir] stringByAppendingPathComponent:[input lastPathComponent]]];
        [movie saveToPath:input flatten:NO];
    }
    //input = [MHUtil uniquePath:[[MHUtil supportDir] stringByAppendingPathComponent:[[movie stringValue] lastPathComponent]]];
    plist = [MHUtil uniquePath:[[MHUtil supportDir] stringByAppendingPathComponent:@"plist.xml"]];
    resource = [[NSBundle mainBundle] pathForResource:@"dvdxchap" ofType:nil];
    resource = [resource stringByDeletingLastPathComponent];
    resource = [resource stringByAppendingPathComponent:@"mhSpriteHelper.app/Contents/MacOS/mhSpriteHelper"];
    NSArray *array = [NSArray arrayWithObjects:input, plist, nil];
    NSDictionary *menu = [movie menu];
    if (!menu) {
        NSBeep();
        return;
    }
    menu = [self plist:menu];
    [menu writeToFile:plist atomically:NO];
    //[menu writeToFile:@"/plist.xml" atomically:NO];
    //[movie saveToPath:input flatten:NO];
    [NSApp beginSheet: progWindow modalForWindow: spriteWindow modalDelegate: nil didEndSelector: nil contextInfo: nil];
    updateTask = [NSTask launchedTaskWithLaunchPath:resource arguments:array];
    [updateTask waitUntilExit];
    updateTask = nil;
    [progWindow orderOut: NULL];
    [progText setStringValue:@""];
    [NSApp endSheet: progWindow];
    if (!DEBUG)
        [[NSFileManager defaultManager] removeFileAtPath:plist handler:nil];
    [spriteMovieView performSelectorOnMainThread:@selector(stop:) withObject:nil waitUntilDone:NO];
    [spriteMovieView performSelectorOnMainThread:@selector(gotoBeginning:) withObject:self waitUntilDone:NO];
    [pool release];
}

-(NSMutableDictionary *)plist:(NSDictionary *)input
{
    NSMutableDictionary *newDict = [NSMutableDictionary dictionary];
    NSArray *tracks = [input objectForKey:@"tracks"];
    NSMutableArray *newTracks = [NSMutableArray array];
    [newDict setObject:newTracks forKey:@"tracks"];
    NSArray *keys, *values;
    NSString *key, *value;
    int i, n = [tracks count];
    long *startTimes = malloc(sizeof(long)*n), totalTime = 0;
    //NSLog(@"%d start times", n);
    for (i=0;i<n;i++) {
        NSString *src = [[tracks objectAtIndex:i] objectForKey:@"bgImageSource"];
        startTimes[i] = totalTime + TRACK_DURATION / 10;
	//NSLog(@"start time %d: %d", i, totalTime);
        if ([src hasSuffix:@".mov"]) {
            NSMovie *aMovie = [[NSMovie alloc] initWithURL:[NSURL fileURLWithPath:src] byReference:NO];
            totalTime += (double)GetMovieDuration([aMovie QTMovie]) / (double)GetMovieTimeScale([aMovie QTMovie]) * (double)GetMovieTimeScale([[spriteMovieView movie] QTMovie]);
	    if ([[tracks objectAtIndex:i] objectForKey:@"loop"])
                totalTime += TRACK_DURATION;
	    //else
            //  totalTime -= TRACK_DURATION;
        } else {
            totalTime += TRACK_DURATION;
        }
    }
    for (i=0;i<n;i++) {
        NSDictionary *track = [tracks objectAtIndex:i];
        NSMutableDictionary *newTrack = [NSMutableDictionary dictionary];
        [newTracks addObject:newTrack];
        keys = [track allKeys];
        values = [track allValues];
        int j;
        for (j=[keys count]-1;j>=0;j--) {
            key = [keys objectAtIndex:j];
            value = [values objectAtIndex:j];
            if (![[self stringClass:value] isEqualToString:@"Unknown!"] && ![key isEqualToString:@"sprites"])
                [newTrack setObject:value forKey:key];
        }
        NSArray *sprites = [track objectForKey:@"sprites"];
        NSMutableArray *newSprites = [NSMutableArray array];
        [(NSMutableDictionary *)[newTracks objectAtIndex:i] setObject:newSprites forKey:@"sprites"];
        for (j=[sprites count]-1;j>=0;j--) {
            NSDictionary *sprite = [sprites objectAtIndex:j];
            NSMutableDictionary *newSprite = [NSMutableDictionary dictionary];
            [newSprites addObject:newSprite];
            keys = [sprite allKeys];
            values = [sprite allValues];
            int k;
            for (k=[keys count]-1;k>=0;k--) {
                key = [keys objectAtIndex:k];
                value = [values objectAtIndex:k];
                if (![[self stringClass:value] isEqualToString:@"Unknown!"] && ![key isEqualToString:@"parent"])
                    [newSprite setObject:value forKey:key];
            }
            NSString *action = [sprite objectForKey:@"Action"];
            if (action) {
                NSScanner *scan = [NSScanner scannerWithString:action];
                [scan setCaseSensitive:NO];
                int pageno = -1;
                if ([scan scanString:@"GoToPage(" intoString:nil]) {
                    [scan scanInt:&pageno];
                    if (pageno >= 0)
                        action = [NSString stringWithFormat:@"GoToTime(%ld);", startTimes[pageno]];
                }
                [newSprite setObject:action forKey:@"Action"];
            }
        }
    }
    /*
    NSMutableArray *trackNames = [NSMutableArray array];
    [newDict setObject:trackNames forKey:@"trackNames"];
    long index;
    Handle h = NewHandleClear(0);
    for (index = 1; index <= GetMovieTrackCount([[spriteMovieView movie] QTMovie]); index++) {
        Track aTrack = GetMovieIndTrack([[spriteMovieView movie] QTMovie], index);
        GetUserData(GetTrackUserData(aTrack), h, kUserDataName, 1);
        int size = GetHandleSize(h);
        char *buffer = (char *)*h;
        NSString *aName = [NSString stringWithCString:buffer length:size];
        [trackNames addObject:aName];
    }
    DisposeHandle(h);
    */
    free(startTimes);
    return newDict;
}

- (NSString *)stringClass:(id)input
{
    if ([input isKindOfClass:[NSString class]])
        return @"String";
    if ([input isKindOfClass:[NSNumber class]])
        return @"Number";
    if ([input isKindOfClass:[NSData class]])
        return @"Data";
    if ([input isKindOfClass:[NSDate class]])
        return @"Date";
    if ([input isKindOfClass:[NSDictionary class]])
        return @"Dictionary";
    if ([input isKindOfClass:[NSArray class]])
        return @"Array";
    return @"Unknown!";
}

- (int)getTrackNumber:(NSString *)trackName
{
    if (![trackName isKindOfClass:[NSString class]]) return 0;
    Movie mov = [[spriteMovieView movie] QTMovie];
    if (!mov) return 0;
    long i;
    Handle h = NewHandleClear(0);
    for (i=1;i<=GetMovieTrackCount(mov);i++) {
        GetUserData(GetTrackUserData(GetMovieIndTrack(mov, i)), h, kUserDataName, 1);
        int size = GetHandleSize(h);
        if (size) {
            char *buffer = (char *)*h;
            NSString *string = [NSString stringWithCString:buffer length:size];
            if (string && [string isEqualToString:trackName]) return i;
        }
    }
    return 0;
}

- (void)submitMenus:(NSData *)data
{
    if (!data) return;
    NSMovie *menuMovie = [NSUnarchiver unarchiveObjectWithData:data];
    if (!menuMovie) return;
    MHMovie *movie = (MHMovie *)[spriteMovieView movie];
    if ([alwaysDelete state])
        [self deleteSpriteTracks:self];
    float thresh = [preloadSize floatValue];
    wide dataSize;
    OSErr theErr = GetMovieDataSize64([menuMovie QTMovie], 0, GetMovieDuration([menuMovie QTMovie]), &dataSize);
    float mbSize = (double)dataSize.lo / 1024.0 / 1024.0;
    Track aTrack;
    long count = GetMovieTrackCount([menuMovie QTMovie]), index = 1;
    if (!theErr && mbSize < thresh)
	for (aTrack = GetMovieIndTrack([menuMovie QTMovie], index); index <= count; aTrack = GetMovieIndTrack([menuMovie QTMovie], index++))
	    SetTrackLoadSettings(aTrack, -1, 0, preloadAlways, 0);
    InsertMovieSegment([menuMovie QTMovie], [movie QTMovie], 0, GetMovieDuration([menuMovie QTMovie]), 0);
    [spriteMovieView performSelectorOnMainThread:@selector(setMovie:) withObject:nil waitUntilDone:NO];
    [spriteMovieView performSelectorOnMainThread:@selector(setMovie:) withObject:movie waitUntilDone:NO];
    [[movie chapters] getChaptersFromMovie:movie];
    //[[movie chapters] offsetAll:(double)GetMovieDuration([menuMovie QTMovie]) / (double)GetMovieTimeScale([movie QTMovie])];
    [movie setSaveInPlaceUnSafe];
}

- (void)setMovieToSize:(NSSize)size
{
    //NSLog(@"Set Movie Width: %d Height: %d", (int)size.width, (int)size.height);
    Rect box;
    if (!size.height)
        size.height = 40;
    box.left = box.top = 0;
    box.right = size.width;
    box.bottom = size.height;
    Movie mov = [[spriteMovieView movie] QTMovie];
    SetMovieBox(mov, &box);
}

- (IBAction)done:(id)sender
{
}

- (IBAction)dump:(id)sender
{
    //[[spriteMovieView movie] saveToPath:@"/out.mov" flatten:NO];
}

- (IBAction)more:(id)sender
{
    if ([moreDrawer state] != NSDrawerClosedState) // don't ask me why but this is right
        [moreButton setTitle:@"More Options"];
    else
        [moreButton setTitle:@"Fewer Options"];
    [moreDrawer toggle:self];
}

- (IBAction)quickMode:(id)sender
{
    BOOL qMode = ![quickModeMenu state];
    [quickModeMenu setState:qMode];
    [theImageView resetCursorRects];
    if ([spriteWindow isVisible])
	[[NSCursor crosshairCursor] push];
    /*
    if (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_2) {
	if (qMode)
	    [[NSCursor crosshairCursor] push];
	else
	    [NSCursor pop];
    }
    */
}

- (IBAction)savePrefs:(id)sender
{
    [self prefs:1];
}

- (IBAction)visibleSwitch:(id)sender
{
    NSMutableDictionary *item = [theOutline selectedItem];
    if ([[item objectForKey:@"isLeaf"] boolValue]) {
        [item setObject:[NSNumber numberWithBool:[visibleSwitch state]] forKey:@"invisible"];
    }
}

- (IBAction)transparencySwitch:(id)sender
{
    BOOL state = [transparencySwitch state];
    if ([transparencyColor respondsToSelector:@selector(setHidden:)])
	[transparencyColor setHidden:!state];
    NSMutableDictionary *item = [theOutline selectedItem];
    if ([[item objectForKey:@"isLeaf"] boolValue]) {
        NSColor *aColor = nil;
        if (!state)
            [item removeObjectForKey:@"keyColor"];
        else {
            aColor = [transparencyColor color];
            NSData *dColor = [NSArchiver archivedDataWithRootObject:aColor];
            if (dColor)
                [item setObject:dColor forKey:@"keyColor"];
        }
        [NSObject cancelPreviousPerformRequestsWithTarget:self];
        [self performSelector:@selector(setTransparencyForSprite:) withObject:item afterDelay:1];
    }
    [[NSColor blackColor] set];
}

- (void) setTransparencyForSprite:(NSMutableDictionary *)sprite
{
    NSArray *keys = [NSArray arrayWithObjects:@"Image", @"Rollover", @"Click", nil];
    NSArray *keys2 = [NSArray arrayWithObjects:@"image", @"rimage", @"cimage", nil];
    int i, n = [keys count];
    for (i=0;i<n;i++) {
        NSImage *image = [self imageForSprite:sprite key:[keys objectAtIndex:i]];
        if (image) {
            [sprite setObject:image forKey:[keys2 objectAtIndex:i]];
            [theImageView setNeedsDisplay:YES];
        }
    }
}

- (IBAction)loopSwitch:(id)sender
{
    BOOL state = [loopSwitch state];
    NSMutableDictionary *item = [theOutline selectedItem];
    if ([[item objectForKey:@"isLeaf"] boolValue])
	item = [item objectForKey:@"parent"];
    if (state)
	[item setObject:[NSNumber numberWithBool:YES] forKey:@"loop"];
    else
	[item removeObjectForKey:@"loop"];
}


// flag: 0 = get from defs, 1 = save to defs
- (void)prefs:(int)flag
{
    //NSLog(@"sprite");
    NSArray *boxes = [NSArray arrayWithObjects: alwaysDelete, byNumber, nil];
    NSArray *strings = [NSArray arrayWithObjects: @"alwaysDeleteSprites", @"spritesByNumber", nil];
    NSUserDefaults *defs = [NSUserDefaults standardUserDefaults];
    int i, n = [boxes count];
    bool state;
    for (i=0;i<n;i++) {
        if (flag == 0) {
            state = [defs boolForKey:[strings objectAtIndex:i]];
            [[boxes objectAtIndex:i] setState:state];
        } else if (flag == 1) {
            [defs setBool:[[boxes objectAtIndex:i] state] forKey:[strings objectAtIndex:i]];
        }
    }
    NSArray *floats = [NSArray arrayWithObjects:preloadSize, wizSize, nil];
    strings = [NSArray arrayWithObjects:@"preloadSize", @"wizSize", nil];
    n = [floats count];
    for (i=0;i<n;i++) {
        if (flag) [defs setFloat:[[floats objectAtIndex:i] floatValue] forKey:[strings objectAtIndex:i]];
        else if ([defs objectForKey:[strings objectAtIndex:i]]) [[floats objectAtIndex:i] setFloatValue:[defs floatForKey:[strings objectAtIndex:i]]];
    }
    [defs synchronize];
    //NSLog(@"sprite");
}

- (void)restorePrefs
{
    [self prefs:0];
}

- (IBAction)deleteSpriteTracks:(id)sender
{
    NSMovie *movie = [spriteMovieView movie];
    Movie mov = [movie QTMovie];
    if (!mov) return;
    OSType type;
    Track track;
    long i, n = GetMovieTrackCount(mov);
    for (i=1;i<=n;i++) {
        track = GetMovieIndTrack(mov, i);
        GetMediaHandlerDescription(GetTrackMedia(track), &type, nil, nil);
        if (type == SpriteMediaType)
            DisposeMovieTrack(track);
    }
    n = GetMovieTrackCount(mov);
    TimeValue offset1, offset2 = GetMovieDuration(mov);
    for (i=1;i<=n;i++) {
        track = GetMovieIndTrack(mov, i);
        offset1 = GetTrackOffset(track);
        if (offset1 < offset2)
            offset2 = offset1;
    }
    DeleteMovieSegment(mov, 0, offset2);
    [spriteMovieView setMovie:nil];
    [spriteMovieView setMovie:movie];
    [(MHMovie *)movie setSaveInPlaceUnSafe];
}

- (void)syncWidgets:(NSMutableDictionary *)sprite
{
    [qScriptBox setString:@""];
    [actionValue setEnabled:NO];
    [actionValue removeAllItems];
    if (![[sprite objectForKey:@"isLeaf"] boolValue]) {
        // disable everything
        [setImageButton setEnabled:NO];
        [actionType setEnabled:NO];
        [actionType selectItemAtIndex:0];
        [transparencySwitch setState:NO];
        [transparencySwitch setEnabled:NO];
	if ([transparencyColor respondsToSelector:@selector(setHidden:)])
	    [transparencyColor setHidden:YES];
        return;
    }
    MHChapters *ch;
    NSMovie *movie = [spriteMovieView movie];
    NSMutableDictionary *menu = [movie menu];
    if (!menu)
        menu = [self newMenu];
    NSArray *tracks = [menu objectForKey:@"tracks"];
    NSString *action = [sprite objectForKey:@"Action"];
    if (!action) [actionType selectItemAtIndex:0];
    else {
        [qScriptBox setString:action];
        NSString *buffer = nil;
        NSScanner *scan = [NSScanner scannerWithString:action];
        [scan setCaseSensitive:NO];
        if ([scan scanString:@"gotochapter(" intoString:nil]) {
            [actionType selectItemAtIndex:1];
            ch = [[[MHChapters alloc] initFromMovie:movie] autorelease];
            [actionValue addItemsWithTitles:[ch chapterNames]];
            [actionValue setEnabled:YES];
            [scan scanUpToString:@")" intoString:&buffer];
            if (buffer && [actionValue itemWithTitle:buffer])
                [actionValue selectItemWithTitle:buffer];
        } else if ([scan scanString:@"// set" intoString:nil]) {
            OSType type = 0;
            [actionValue setEnabled:YES];
            if ([scan scanString:@"audio track" intoString:nil]) {
                type = SoundMediaType;
                [actionType selectItemAtIndex:2];
            } else if ([scan scanString:@"text track" intoString:nil]) {
                type = TextMediaType;
                [actionType selectItemAtIndex:3];
            }
            if (type)
                [actionValue addItemsWithTitles:[self getTracksOfType:type]];
            while (![scan isAtEnd]) {
                buffer = nil;
                [scan scanPastString:@"tracknamed("];
                [scan scanUpToString:@")" intoString:&buffer];
                [scan scanPastString:@"setenabled("];
                if ([scan scanString:@"yes" intoString:nil] || [scan scanString:@"true" intoString:nil]) {
                    if (buffer && [actionValue itemWithTitle:buffer])
                        [actionValue selectItemWithTitle:buffer];
                    break;
                }
            }
        } else if ([scan scanString:@"stepforward" intoString:nil]) {
            [actionType selectItemAtIndex:4];
        } else if ([scan scanString:@"stepbackward" intoString:nil]) {
            [actionType selectItemAtIndex:5];
        } else if ([scan scanString:@"gotopage(" intoString:nil]) {
            [actionType selectItemAtIndex:6];
            int i, n = [tracks count];
            for (i=0;i<n;i++)
                [actionValue addItemWithTitle:[NSString stringWithFormat:@"%d", i]];
            [actionValue setEnabled:YES];
            int v = 0;
            [scan scanInt:&v];
            if (v >= 0 && v < [actionValue numberOfItems])
                [actionValue selectItemAtIndex:v];
        }
    }
    
    NSData *dColor = [sprite objectForKey:@"keyColor"];
    if (dColor) {
        NSColor *kColor = [NSUnarchiver unarchiveObjectWithData:dColor];
        [transparencySwitch setState:YES];
	if ([transparencyColor respondsToSelector:@selector(setHidden:)])
	    [transparencyColor setHidden:NO];
        [transparencyColor setColor:kColor];
    } else {
        [transparencySwitch setState:NO];
	if ([transparencyColor respondsToSelector:@selector(setHidden:)])
	    [transparencyColor setHidden:YES];
    }
    [visibleSwitch setState:[[sprite objectForKey:@"invisible"] boolValue]];
}

- (IBAction)updateWidgets:(id)sender
{
    int row = [theOutline selectedRow];
    NSMutableDictionary *item = nil;
    MHChapters *ch;
    NSMovie *movie = [spriteMovieView movie];
    NSMutableDictionary *menu = [movie menu];
    if (!menu)
        menu = [self newMenu];
    NSArray *tracks = [menu objectForKey:@"tracks"];
    int i, n;
    
    if (-1 != row)
        item = [theOutline itemAtRow:row];
    if (!item) return;
    if (![[item objectForKey:@"isLeaf"] boolValue]) {
	[loopSwitch setState:[[item objectForKey:@"loop"] boolValue]];
        // disable everything
        [setImageButton setEnabled:NO];
        [actionType setEnabled:NO];
        [actionType selectItemAtIndex:0];
        [actionValue setEnabled:NO];
        [actionValue removeAllItems];
        [viewImageButton setEnabled:NO];
	[transparencySwitch setState:NO];
        [transparencySwitch setEnabled:NO];
	if ([transparencyColor respondsToSelector:@selector(setHidden:)])
	    [transparencyColor setHidden:YES];
        [loadTemplateButton setEnabled:NO];
        [saveTemplateButton setEnabled:NO];
        [visibleSwitch setState:NO];
        [visibleSwitch setEnabled:NO];
    } else {
        [setImageButton setEnabled:YES];
        [viewImageButton setEnabled:YES];
        [actionType setEnabled:YES];
        [transparencySwitch setEnabled:YES];
        [loadTemplateButton setEnabled:YES];
        [saveTemplateButton setEnabled:YES];
        [visibleSwitch setEnabled:YES];
	if ([[[item objectForKey:@"parent"] objectForKey:@"bgImageSource"] hasSuffix:@".mov"]) {
	    [transparencySwitch setState:NO];
	    [transparencySwitch setEnabled:NO];
	    if ([transparencyColor respondsToSelector:@selector(setHidden:)])
		[transparencyColor setHidden:YES];
	}
        
        NSArray *tracksOfType;
        
        int actionSelection = [actionType indexOfSelectedItem];
        switch (actionSelection) {
            case 1: // gotoChapter
            [actionValue removeAllItems];
            ch = [[MHChapters alloc] initFromMovie:movie];
            [actionValue addItemsWithTitles:[ch chapterNames]];
            [actionValue setEnabled:YES];
            if ([actionValue numberOfItems])
                [actionValue selectItemAtIndex:0];
            break;
            case 2: // set audio track
            [actionValue removeAllItems];
            [actionValue setEnabled:YES];
            tracksOfType = [self getTracksOfType:SoundMediaType];
            if (tracksOfType && [tracksOfType count])
                [actionValue addItemsWithTitles:tracksOfType];
            else
                [actionValue setEnabled:NO];
            if ([actionValue numberOfItems])
                [actionValue selectItemAtIndex:0];
            break;
            case 3: // set text track
            [actionValue removeAllItems];
            [actionValue setEnabled:YES];
            tracksOfType = [self getTracksOfType:TextMediaType];
            if (tracksOfType && [tracksOfType count])
                [actionValue addItemsWithTitles:tracksOfType];
            else
                [actionValue setEnabled:NO];
            if ([actionValue numberOfItems])
                [actionValue selectItemAtIndex:0];
            break;
            case 6: // go to page
            [actionValue removeAllItems];
            n = [tracks count];
            for (i=0;i<n;i++) {
                NSString *name = [[tracks objectAtIndex:i] objectForKey:@"Name"];
                if (!name) name = @"";
                [actionValue addItemWithTitle:[NSString stringWithFormat:@"%d. %@",i,name]];
            }
            [actionValue setEnabled:YES];
            if ([actionValue numberOfItems])
                [actionValue selectItemAtIndex:0];
            break;
            default: // none, step forward, step backward
            [actionValue removeAllItems];
            [actionValue setEnabled:NO];
            break;
        }
    }
}

- (NSArray *)chapterNames
{
    MHChapters *ch = [[MHChapters alloc] initFromMovie:[spriteMovieView movie]];
    return [ch chapterNames];
}

- (NSArray *)pageNumbers
{
    NSDictionary *menu = [[spriteMovieView movie] menu];
    if (!menu)
	menu = [self newMenu];
    NSArray *tracks = [menu objectForKey:@"tracks"];
    int i, n = [tracks count];
    NSMutableArray *array = [NSMutableArray array];
    for (i=0;i<n;i++) {
	NSString *name = [[tracks objectAtIndex:i] objectForKey:@"Name"];
	if (!name) name = @"";
	[array addObject:[NSString stringWithFormat:@"%d. %@", i, name]];
    }
    return array;
}

- (IBAction)updateData:(id)sender
{
    //Movie mov = [[spriteMovieView movie] QTMovie];
    //Rect box;
    //GetMovieBox(mov, &box);
    NSMutableDictionary *item = [theOutline selectedItem];
    if (!item || ![[item objectForKey:@"isLeaf"] boolValue]) return;
    int type = [actionType indexOfSelectedItem];
    NSString *value = [actionValue titleOfSelectedItem];
    NSString *actionString = [self stringForAction:type value:value];
    if (![actionString length]) [item removeObjectForKey:@"Action"];
    else [item setObject:actionString forKey:@"Action"];
    [self syncWidgets:item];
    [theImageView setNeedsDisplay:YES];
}
    
- (NSString *)stringForAction:(int)type value:(NSString *)value
{
    NSString *actionString = @"";
    NSMutableString *name = nil;
    BOOL hit = NO;
    int i, n;
    MHTrack *track = nil;
    MHMovie *movie = (MHMovie *)[spriteMovieView movie];
    switch (type) {
        case 0: // none
            break;
        case 1: // go to chapter
            actionString = @"GoToChapter(";
	    if (value) actionString = [actionString stringByAppendingString:value];
            actionString = [actionString stringByAppendingString:@");\nStartPlaying;"];
	    break;
        case 2: // set audio track
            actionString = @"// set audio track\n";
            n = [movie trackCount];
            for (i=0;i<n;i++) {
                track = [movie trackAtIndex:i];
                if ([track isSoundType] && [track name]) {
                    name = [NSMutableString stringWithString:[track name]];
                    hit = [value isEqualToString:name];
                    if ([byNumber state])
                        actionString = [actionString stringByAppendingFormat:@"TrackAtIndex(%d)", i];
                    else {
                        [name replaceOccurrencesOfString:@")" withString:@"\\)" options:NSLiteralSearch range:NSMakeRange(0, [name length])];
                        actionString = [actionString stringByAppendingFormat:@"TrackNamed(%@)", name];
                    }
                    actionString = [actionString stringByAppendingFormat:@".SetEnabled(%@);\n", (hit ? @"YES" : @"NO")];
                }
            }
	    break;
        case 3: // set text track
            actionString = @"// set text track\n";
            n = [movie trackCount];
            for (i=0;i<n;i++) {
                track = [movie trackAtIndex:i];
                if ([[track type] isEqualToString:@"Text"] && [track name]) {
                    name = [NSMutableString stringWithString:[track name]];
                    hit = [value isEqualToString:name];
                    if ([byNumber state]) actionString = [actionString stringByAppendingFormat:@"TrackAtIndex(%d)", i];
                    else {
                        [name replaceOccurrencesOfString:@")" withString:@"\\)" options:NSLiteralSearch range:NSMakeRange(0, [name length])];
                        actionString = [actionString stringByAppendingFormat:@"TrackNamed(%@)", name];
                    }
                    actionString = [actionString stringByAppendingFormat:@".SetEnabled(%@);\n", (hit ? @"YES" : @"NO")];
                }
            }
	    break;
        case 4: // step forward
            actionString = @"StepForward;";
            break;
        case 5: // step backward
            actionString = @"StepBackward;";
            break;
        case 6: // go to page
            actionString = [NSString stringWithFormat:@"GoToPage(%d);", [value intValue]];
            break;
        default: break;
    }
    return actionString;
}

- (IBAction)selectActionType:(id)sender
{
    [self updateWidgets:nil];
    [self updateData:nil];
    [theOutline reloadData];
}

- (IBAction)setAction:(id)sender
{
    NSString *title = [sender title];
    NSArray *titles = [NSArray arrayWithObjects:@"None", @"Go To Chapter", @"Set Audio Track", @"Set Text Track", @"Step Forward", @"Step Backward", @"Go To Page", nil];
    NSArray *actions = [NSArray arrayWithObjects:@"", @"GoToChapter()", @"// set audio track\nTrackNamed().SetEnabled();", @"// set text track\nTrackNamed().SetEnabled();", @"StepForward;", @"StepBackward;", @"GoToPage(0);", nil];
    int i, n = [titles count];
    for (i=0;i<n;i++) {
	if ([title isEqualToString:[titles objectAtIndex:i]])
	[(NSMutableDictionary *)[self selectedTableItem] setObject:[actions objectAtIndex:i] forKey:@"Action"];
    }
    [theOutline reloadData];
    [self updateWidgets:nil];
    [self syncWidgets:(NSMutableDictionary *)[theOutline selectedItem]];
}

- (IBAction)selectActionValue:(id)sender
{
    [self updateData:nil];
}

- (IBAction)setValue:(id)sender
{
    NSString *title = [sender title];
    int i = 0, n = 0;
    if (!title) return;
    if ([sender isKindOfClass:[NSMenuItem class]]) {
        NSMenu *submenu = [sender menu];
        NSMenu *supermenu = [submenu supermenu];
        n = [supermenu numberOfItems];
        for (i=0;i<n;i++) {
            if ([[supermenu itemAtIndex:i] submenu] == submenu) {
                //[self setAction:[supermenu itemAtIndex:i]];
                break;
            }
        }
    }
    NSString *string = nil;
    if (i<n)
        string = [self stringForAction:i value:title];
    if (string)
        [(NSMutableDictionary *)[self selectedTableItem] setObject:string forKey:@"Action"];
    else
        [(NSMutableDictionary *)[self selectedTableItem] removeObjectForKey:@"Action"];
    [theOutline reloadData];
    [self syncWidgets:(NSMutableDictionary *)[theOutline selectedItem]];
}

- (IBAction)viewFrame:(id)sender
{
    id item = [theOutline itemAtRow:[theOutline selectedRow]];
    if (![[item objectForKey:@"isLeaf"] boolValue]) return;
    int sel = [imageSelect indexOfSelectedItem];
    NSString *buffer = nil;
    TimeValue tv = 0;
    switch (sel) {
        case 0: // main
            buffer = [item objectForKey:@"Image"];
            break;
        case 1: // rollover
            buffer = [item objectForKey:@"Rollover"];
            break;
        case 2: // click
            buffer = [item objectForKey:@"Click"];
            break;
        default: break;
    }
    if (!buffer) return;
    if ([buffer isKindOfClass:[NSString class]] && [buffer hasPrefix:@"/"]) return;
    tv = [buffer intValue];
    TimeRecord tr;
    Movie mov = [[spriteMovieView movie] QTMovie];
    GetMovieTime(mov, &tr);
    tr.value.lo = tv;
    SetMovieTime(mov, &tr);
}

- (IBAction)setBackground:(id)sender
{
    NSMutableDictionary *item = [theOutline itemAtRow:[theOutline selectedRow]];
    if ([[item objectForKey:@"isLeaf"] boolValue])
        item = [item objectForKey:@"parent"];
    if (!item) return;
    int type = [imageType selectedRow];
    if (type == 0) { // movie frame
        NSMovie *aMovie = [spriteMovieView movie];
        Movie mov = [aMovie QTMovie];
        //id helper = [MHUtil imageHelper];
        int height = [spriteMovieView sizeForMagnification:1.0].height - 16;
        //[helper setHeight:height];
        //[helper openMovie:[aMovie URL]];
        TimeRecord tr;
        TimeValue tv = GetMovieTime(mov, &tr);
        //NSData *data = [helper getImage:tv sender:@"mhSprite"];
        //if (data) {
            //NSImage *image = [NSUnarchiver unarchiveObjectWithData:data];
            //if (image)
            	//[item setObject:image forKey:@"bgImage"];
        [item setObject:[NSString stringWithFormat:@"%d",tv] forKey:@"bgImageSource"];
        NSImage *image = [self imageFromFrame:tv height:height];
        if (image)
            [item setObject:image forKey:@"bgImage"];
            [item setObject:[NSString stringWithFormat:@"%d",tv] forKey:@"Image"];
        //}
    } else { // file
        NSOpenPanel *theOpenPanel = [NSOpenPanel openPanel];
        int result;
        NSString *choice;
    
        [theOpenPanel setCanChooseFiles:YES];
        [theOpenPanel setCanChooseDirectories:NO];
        [theOpenPanel setResolvesAliases:YES];
        [theOpenPanel setAllowsMultipleSelection:NO];
        //NSArray *extensions = [NSArray arrayWithObjects:@"pict", @"PICT", @"pct", @"PCT", @"mov", nil];
        result = [theOpenPanel runModalForDirectory:nil file:nil types:nil];
        if (result == NSOKButton) {
            choice = [[theOpenPanel filenames] objectAtIndex:0];
            NSImage *theImage = nil;
            if ([choice hasSuffix:@".mov"]) {
                MHMovie *menuMovie = [[[MHMovie alloc] initWithPath:choice] autorelease];
		Rect box;
		GetMovieBox([menuMovie QTMovie], &box);
                TimeValue time = GetMovieDuration([menuMovie QTMovie]) / 2;
                theImage = [self imageFromFrame:time height:box.bottom - box.top movieURL:[menuMovie URL]];
            } else
                theImage = [[NSImage alloc] initWithContentsOfFile:choice];
            if (theImage) {
                [item setObject:theImage forKey:@"bgImage"];
                [item setObject:choice forKey:@"bgImageSource"];
                [item setObject:choice forKey:@"Image"];
            }
    	}
    }
    [self outlineViewSelectionDidChange:nil];
    //[theImageView setNeedsDisplay:YES];
}

- (NSImage *)imageFromFrame:(TimeValue)time height:(int)height
{
    return [self imageFromFrame:time height:height movie:(MHMovie *)[spriteMovieView movie]];
}

- (NSImage *)imageFromFrame:(TimeValue)time height:(int)height movie:(MHMovie *)movie
{
    return [self imageFromFrame:time height:height movieURL:[movie URL]];
}

- (NSImage *)imageFromFrame:(TimeValue)time height:(int)height movieURL:(NSURL *)url
{
    id helper = [MHUtil imageHelper];
    if (!height)
        height = 80;
    [helper setHeight:height];
    [helper openMovie:url];
    NSData *data = [helper getImage:time sender:@"mhSprite"];
    if (data)
        return [NSUnarchiver unarchiveObjectWithData:data];
    return nil;
}

- (BOOL)hasSprites
{
    NSArray *pages = [[[spriteMovieView movie] menu] objectForKey:@"tracks"];
    int i, n = [pages count];
    for (i=0;i<n;i++) {
        if ([(NSArray *)[[pages objectAtIndex:i] objectForKey:@"sprites"] count]) return YES;
        if ([[pages objectAtIndex:i] objectForKey:@"bgImageSource"]) return YES;
    }
    return NO;
}

- (IBAction)wizard:(id)sender
{
    if ([self hasSprites]) {
        int choice = NSRunAlertPanel(@"Erase Existing Sprites?", @"The Wizard overwrites existing sprites.  Do you want to overwrite?", @"Overwrite", @"Cancel", nil);
        if (choice == NSAlertAlternateReturn) return;
    }
    [progText setStringValue:@""];
    [progBar setDoubleValue:0];
    [NSApp beginSheet: progWindow modalForWindow: spriteWindow modalDelegate: nil didEndSelector: nil contextInfo: nil];
    [NSThread detachNewThreadSelector:@selector(wizardThreaded) toTarget:self withObject:nil];
}

- (void)wizardThreaded
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    MHMovie *aMovie = (MHMovie *)[spriteMovieView movie];
    if (!aMovie) goto bail;
    cancelled = NO;
    workingPath = [[aMovie objectForKey:@"File Path"] stringByDeletingLastPathComponent];
    NSFileManager *fm = [NSFileManager defaultManager];
    float scalar = [wizSize floatValue];
    if (!scalar) scalar = 1;
    int width = naturalSize.width / 3 - 20;
    int height = naturalSize.height * width / naturalSize.width;
    width = (float) width * scalar;
    height = (float) height * scalar;
    id helper = [MHUtil imageHelper];
    [helper setHeight:height];
    //[fHeight setIntValue:height];
    [helper openMovie:[aMovie URL]];
    MHChapters *ch = [[[MHChapters alloc] initFromMovie:aMovie] autorelease];
    NSMutableDictionary *track;
    NSMutableDictionary *menu = [aMovie menu];
    if (!menu)
        menu = [self newMenu];
    int tally = 0, i, n, count = [ch numberOfRowsInTableView:nil];
    n = [(NSArray *)[menu objectForKey:@"tracks"] count];
    NSMutableArray *bgs = [NSMutableArray array], *bgss = [NSMutableArray array];
    for (i=0;i<n;i++) {
        id obj = [[[menu objectForKey:@"tracks"] objectAtIndex:i] objectForKey:@"bgImage"];
        if (obj)
            [bgs addObject:obj];
        else
            [bgs addObject:[NSNull null]];
    }
    for (i=0;i<n;i++) {
        id obj = [[[menu objectForKey:@"tracks"] objectAtIndex:i] objectForKey:@"bgImageSource"];
        if (obj)
            [bgss addObject:obj];
        else
            [bgss addObject:[NSNull null]];
    }
    
    BOOL first = YES;
    int numPages = count / 6;
    if (count % 6)
        numPages++;
    if (count) {
        [[menu objectForKey:@"tracks"] removeAllObjects];
        // scene selection
        i = 0;
        while (YES) {
            if (cancelled) goto bail;
            if (i+6 <= count)
                n = 6;
            else
                n = count % 6;
            //[progText setStringValue:[NSString stringWithFormat:@"Creating Page %d of %d", tally+1, numPages]];
            [progText performSelectorOnMainThread:@selector(setStringValue:) withObject:[NSString stringWithFormat:@"Creating Page %d of %d", tally+1, numPages] waitUntilDone:NO];
            //[progBar setDoubleValue:(double)(i+1)/(double)count];
            [progBar performSelectorOnMainThread:@selector(setDoubleValueObject:) withObject:[NSNumber numberWithDouble:(double)(i+1)/(double)count] waitUntilDone:NO];
            track = [self addPageWithChapters:ch startingAt:i count:n];
            if (cancelled) goto bail;
            NSColor *color = [bgColor color];
            if (color)
                [track setObject:[NSArchiver archivedDataWithRootObject:color] forKey:@"color"];
            NSString *bgpic = [workingPath stringByAppendingPathComponent:@"bg.pct"];
            if ([fm fileExistsAtPath:bgpic]) {
                NSImage *im = [[NSImage alloc] initWithContentsOfFile:bgpic];
                if (im) {
                    [track setObject:bgpic forKey:@"bgImageSource"];
                    [track setObject:im forKey:@"bgImage"];
                }
            }
            if (cancelled) goto bail;
            if (first)
                first = NO;
            else
                [self addNavButton:@"prev" destination:(tally - 1)];
            if (tally < numPages - 1) {
                [self addNavButton:@"next" destination:(tally + 1)];
            }
            i += 6;
            if (i >= count) break;
            tally++;
        }
    }
    
    NSArray *audioTracks = [self getTracksOfType:SoundMediaType];
    if (1 < [audioTracks count]) {
        // audio track selection
    }
    NSArray *textTracks = [self getTracksOfType:TextMediaType];
    if (1 < [textTracks count]) {
        // text track selection
    }
    
    n = [(NSArray *)[menu objectForKey:@"tracks"] count];
    //m = [bgs count];
    id obj, defIm = nil, defSource = nil;
    for (i=0;i<n; i++) {
        track = [[menu objectForKey:@"tracks"] objectAtIndex:i];
        if (i >= [bgs count]) {
            if (defIm)
                [track setObject:defIm forKey:@"bgImage"];
            if (defSource)
                [track setObject:defSource forKey:@"bgImageSource"];
        } else {
            obj = [bgs objectAtIndex:i];
            if (![obj isKindOfClass:[NSNull class]]) {
                [track setObject:obj forKey:@"bgImage"];
                if (!defIm)
                    defIm = obj;
            }
            obj = [bgss objectAtIndex:i];
            if (![obj isKindOfClass:[NSNull class]]) {
                [track setObject:obj forKey:@"bgImageSource"];
                if (!defSource)
                    defSource = obj;
            }
        }
    }
    
bail:
    //[theOutline reloadData];
    [theOutline performSelectorOnMainThread:@selector(reloadData) withObject:nil waitUntilDone:NO];
    //[theImageView setNeedsDisplay:YES];
    [theImageView performSelectorOnMainThread:@selector(setNeedsDisplayObject:) withObject:[NSNumber numberWithBool:YES] waitUntilDone:NO];
    
    //[progWindow orderOut: NULL];
    [progWindow performSelectorOnMainThread:@selector(orderOut:) withObject:self waitUntilDone:NO];
    //[NSApp endSheet: progWindow];
    [NSApp performSelectorOnMainThread:@selector(endSheet:) withObject:progWindow waitUntilDone:NO];
    
    [pool release];
}

- (IBAction)cancel:(id)sender
{
    cancelled = YES;
    [updateTask terminate];
}

- (IBAction)loadTemplate:(id)sender
{
    NSOpenPanel *theOpenPanel = [NSOpenPanel openPanel];
    NSMutableDictionary *item = [theOutline itemAtRow:[theOutline selectedRow]];
    if (!item || ![[item objectForKey:@"isLeaf"] boolValue]) return;
    
    [theOpenPanel setCanChooseFiles:YES];
    [theOpenPanel setCanChooseDirectories:NO];
    [theOpenPanel setResolvesAliases:YES];
    [theOpenPanel setAllowsMultipleSelection:NO];
    if (NSOKButton != [theOpenPanel runModalForDirectory:nil file:nil types:[NSArray arrayWithObject:@"mhsprite"]]) return;
    NSString *path = [[theOpenPanel filenames] objectAtIndex:0];
    [self readSpriteTemplateFromPath:path toSprite:item];
    [theOutline reloadData];
    [theImageView setNeedsDisplay:YES];
    [self updateWidgets:nil];
    [self syncWidgets:item];
}

- (IBAction)saveTemplate:(id)sender
{
    NSSavePanel *sp = [NSSavePanel savePanel];
    int runResult = [sp runModalForDirectory:nil file:@"Sprite Template.mhsprite"];
    if (runResult != NSOKButton) return;
    NSString *path = [sp filename];
    if (![path hasSuffix:@".mhsprite"])
        path = [path stringByAppendingString:@".mhsprite"];
    NSDictionary *item = [theOutline itemAtRow:[theOutline selectedRow]];
    if (!item || ![[item objectForKey:@"isLeaf"] boolValue]) return;
    [self writeSpriteTemplate:item toPath:path];
}

- (BOOL)writeSpriteTemplate:(NSDictionary *)sprite toPath:(NSString *)path
{
    BOOL failure = NO;
    NSFileManager *fm = [NSFileManager defaultManager];
    if (![fm createDirectoryAtPath:path attributes:nil]) {
        NSLog(@"failed to create mhsprite dir");
        failure = YES;
    }
    NSArray *anArray = [NSArray arrayWithObjects:@"Image", @"Rollover", @"Click", nil];
    NSMutableDictionary *info = [NSMutableDictionary dictionary];
    NSString *aPath, *newPath;
    int i;
    for (i=0;i<3;i++) {
        aPath = [sprite objectForKey:[anArray objectAtIndex:i]];
        if ([aPath hasPrefix:@"/"]) {
            newPath = [path stringByAppendingPathComponent:[anArray objectAtIndex:i]];
            if (![fm copyPath:aPath toPath:newPath handler:nil]) {
                NSLog(@"faile to copy file:\n%@", aPath);
                failure = YES;
            }
        } else
            [info setObject:aPath forKey:[anArray objectAtIndex:i]];
    }
    anArray = [NSArray arrayWithObjects:@"Action", @"X", @"Y", @"Width", @"Height", @"Name", @"keyColor", nil];
    for (i=0;i<[anArray count];i++) {
        id aValue = [sprite objectForKey:[anArray objectAtIndex:i]];
        if (aValue)
            [info setObject:aValue forKey:[anArray objectAtIndex:i]];
    }
    newPath = [path stringByAppendingPathComponent:@"info.xml"];
    if (![info writeToFile:newPath atomically:YES]) {
        NSLog(@"failed to write info file");
        failure = YES;
    }
    return !failure;
}

- (BOOL)readSpriteTemplateFromPath:(NSString *)path toSprite:(NSMutableDictionary *)aSprite
{
    NSString *aPath = [path stringByAppendingPathComponent:@"info.xml"];
    NSDictionary *aDict = [NSDictionary dictionaryWithContentsOfFile:aPath];
    if (!aDict) return NO;
    
    NSArray *anArray = [NSArray arrayWithObjects:@"Image", @"Rollover", @"Click", nil];
    NSArray *imageArray = [NSArray arrayWithObjects:@"image", @"rimage", @"cimage", nil];
    int i;
    for (i=0;i<3;i++) {
        NSString *newPath = [path stringByAppendingPathComponent:[anArray objectAtIndex:i]];
        NSImage *newImage = [[NSImage alloc] initWithContentsOfFile:newPath];
        if (newImage) {
            [aSprite setObject:newPath forKey:[anArray objectAtIndex:i]];
            [aSprite setObject:newImage forKey:[imageArray objectAtIndex:i]];
        } else {
            newPath = [aDict objectForKey:[anArray objectAtIndex:i]];
            if (newPath)
                [aSprite setObject:newPath forKey:[anArray objectAtIndex:i]];
        }
    }
    anArray = [aDict allKeys];
    for (i=0;i<[anArray count];i++) {
        id value = [aDict objectForKey:[anArray objectAtIndex:i]];
        if (value)
            [aSprite setObject:value forKey:[anArray objectAtIndex:i]];
    }
    return YES;
}

- (NSMutableDictionary *)addPageWithChapters:(MHChapters *)ch startingAt:(int)index count:(int)count
{
    int x = 0, y = 0;
    float scalar = [wizSize floatValue];
    if (!scalar) scalar = 1;
    int width = naturalSize.width / 3 - 20;
    int height = naturalSize.height * width / naturalSize.width;
    width = (float) width * scalar;
    height = (float) height * scalar;
    MHMovie *aMovie = (MHMovie *)[spriteMovieView movie];
    Movie mov = [aMovie QTMovie];
    if (!mov) return nil;
    [self newPage:nil];
    int i, j;
    NSImage *image;
    NSData *data;
    id helper = [MHUtil imageHelper];
    TimeValue tv, tv2, originalTV;
    TimeRecord tr;
    originalTV = GetMovieTime(mov, &tr);
    NSMutableDictionary *s;
    NSMutableDictionary *menu = [aMovie menu];
    if (!menu)
        menu = [self newMenu];
    NSMutableDictionary *track = [[menu objectForKey:@"tracks"] lastObject];
    OSType theType = VideoMediaType;
    short flags = nextTimeEdgeOK + nextTimeMediaSample;
    for (i = index; i < index + count; i++) {
        //[progText setStringValue:[NSString stringWithFormat:@"Chapter %d of %d", i, [ch numberOfRowsInTableView:nil]]];
        if (cancelled) break;
        [progText performSelectorOnMainThread:@selector(setStringValue:) withObject:[NSString stringWithFormat:@"Chapter %d of %d", i, [ch numberOfRowsInTableView:nil]] waitUntilDone:NO];
        //[progBar setDoubleValue:(double)(i+1)*100/(double)[ch numberOfRowsInTableView:nil]];
        [progBar performSelectorOnMainThread:@selector(setDoubleValueObject:) withObject:[NSNumber numberWithDouble:(double)(i+1)/(double)[ch numberOfRowsInTableView:nil]] waitUntilDone:NO];
        tv = [ch timeForChapterAtIndex:i] * (double)GetMovieTimeScale(mov);
        for (j=0;j<4;j++) {
            GetMovieNextInterestingTime(mov, flags, 1, &theType, tv, 1, &tv, nil);
            tr.value.lo = tv;
            SetMovieTime(mov, &tr);
            MoviesTask(mov, 0);
        }
        if (i== index + count - 1)
            tv2 = GetMovieDuration(mov);
        else
            tv2 = [ch timeForChapterAtIndex:i+1] * (double)GetMovieTimeScale(mov);
        tv2 = tv + (tv2 - tv) / 2;
        s = [[NSMutableDictionary alloc] init];
        [[track objectForKey:@"sprites"] addObject:s];
        //tr.value.lo = tv;
        //SetMovieTime(mov, &tr);
        //MoviesTask(mov, 0);
        [s setObject:@"Sprite" forKey:@"Name"];
        [s setObject:[NSString stringWithFormat:@"%d", tv] forKey:@"Image"];
        [s setObject:[NSString stringWithFormat:@"%d", tv2] forKey:@"Rollover"];
        [s setObject:[NSNumber numberWithBool:YES] forKey:@"isLeaf"];
        [s setObject:track forKey:@"parent"];
        data = [helper getImage:tv sender:@"mhSprite"];
        if (data) {
            image = [NSUnarchiver unarchiveObjectWithData:data];
            [s setObject:image forKey:@"image"];
        }
        switch (count) {
            case 1:
                x = (naturalSize.width - width) / 2;
                break;
            case 2: case 4:
                if (i % 2)
                    x = naturalSize.width * 3 / 4 - width / 2;
                else
                    x = naturalSize.width / 4 - width / 2;
                break;
            case 3: case 5: case 6:
                if (i % 3 == 0)
                    x = naturalSize.width / 6 - width / 2 + 7;
                else if (i % 3 == 1)
                    x = naturalSize.width / 2 - width / 2;
                else if (i % 3 == 2)
                    x = naturalSize.width * 5 / 6 - width / 2 - 5;
                break;
            default: break;
        }
        if (count < 4)
            y = (naturalSize.height - height) / 2;
        else {
            if (i - index < (count + 1) / 2 || (1 - index == 2 && count == 5))
                y = height / 2 - 5;
            else
                y = naturalSize.height - height * 3 / 2 - 5;
        }
        [s setObject:[NSString stringWithFormat:@"%d", x] forKey:@"X"];
        [s setObject:[NSString stringWithFormat:@"%d", y] forKey:@"Y"];
        [s setObject:[NSString stringWithFormat:@"%d", width] forKey:@"Width"];
        [s setObject:[NSString stringWithFormat:@"%d", height] forKey:@"Height"];
        NSString *name = [[ch chapterNames] objectAtIndex:i];
        if (!name) name = @"";
        [s setObject:[NSString stringWithFormat:@"GoToChapter(%@);\nStartPlaying;", name] forKey:@"Action"];
    }
    tr.value.lo = originalTV;
    SetMovieTime(mov, &tr);
    return track;
}

- (void)addNavButton:(NSString *)nav destination:(TimeValue)dest
{
    NSMutableDictionary *menu = [[spriteMovieView movie] menu];
    if (!menu)
        menu = [self newMenu];
    
    NSMutableDictionary *aSprite = [NSMutableDictionary dictionary];
    NSMutableArray *tracks = [menu objectForKey:@"tracks"];
    if (![tracks count])
        [self newPage:nil];
    NSMutableDictionary *track = [tracks lastObject];
    [[track objectForKey:@"sprites"] addObject:aSprite];
    int x = (int)naturalSize.width - 24;
    if ([nav isEqualToString:@"prev"]) x -= 24;
    int y = (int)naturalSize.height - 24;
    [aSprite setObject:[NSString stringWithFormat:@"%d",x] forKey:@"X"];
    [aSprite setObject:[NSString stringWithFormat:@"%d",y] forKey:@"Y"];
    [aSprite setObject:[NSString stringWithFormat:@"%d",16] forKey:@"Width"];
    [aSprite setObject:[NSString stringWithFormat:@"%d",16] forKey:@"Height"];
    [aSprite setObject:[NSNumber numberWithBool:YES] forKey:@"isLeaf"];
    [aSprite setObject:track forKey:@"parent"];
    NSString *path = [[NSBundle mainBundle] pathForResource:nav ofType:@"pct"];
    [aSprite setObject:path forKey:@"Image"];
    [aSprite setObject:[NSImage imageNamed:nav] forKey:@"image"];
    [aSprite setObject:nav forKey:@"Name"];
    [aSprite setObject:[NSString stringWithFormat:@"GoToPage(%ld);", dest] forKey:@"Action"];
}

- (NSArray *)getTracksOfType:(OSType)type
{
    Movie mov = [[spriteMovieView movie] QTMovie];
    Track track = nil;
    long index = 1;
    int size;
    Handle h = NewHandleClear(0);
    char *buffer;
    NSString *aTrackName, *typeName = @"";
    switch (type) {
        case SoundMediaType: typeName = @"Sound Track "; break;
        case TextMediaType: typeName = @"Text Track "; break;
        case VideoMediaType: typeName = @"Video Track "; break;
        case SpriteMediaType: typeName = @"Sprite Track "; break;
        case MPEGMediaType: typeName = @"MPEG Track "; break;
        default: typeName = @"";
    }
    NSMutableArray *array = [[NSMutableArray alloc] init];
    if (!type) track = GetMovieIndTrack(mov, index++);
    else track = GetMovieIndTrackType(mov, index++, type, movieTrackMediaType);
    while (track) {
        GetUserData(GetTrackUserData(track), h, kUserDataName, 1);
        size = GetHandleSize(h);
        if (size) {
            buffer = (char *)*h;
            aTrackName = [NSString stringWithCString:buffer length:size];
            if (aTrackName)
                [array addObject:aTrackName];
        } else {
            aTrackName = [NSString stringWithFormat:@"%@%d", typeName, [array count] + 1];
            buffer = malloc(sizeof(char) * ([aTrackName length] + 1));
            [aTrackName getCString:buffer];
            SetUserDataItem(GetTrackUserData(track), buffer, [aTrackName length], kUserDataName, 1);
            [array addObject:aTrackName];
            free(buffer);
        }
        if (!type) track = GetMovieIndTrack(mov, index++);
        else track = GetMovieIndTrackType(mov, index++, type, movieTrackMediaType);
    }
    DisposeHandle(h);
    return array;
}

- (void)awakeFromNib
{
    //NSLog(@"MHSprite");
    updateTask = nil;
    loadLock = [[NSLock alloc] init];
    mhSpriteNibInstance = self;
    isAwake = YES;
    [actionValue setTarget:self];
    [(NSPopUpButton *)actionValue setAction:@selector(selectActionValue:)];
    [self restorePrefs];
    //[theOutline restoreFromAutosave];
    
    NSConnection *theConnection = [NSConnection defaultConnection];
    [theConnection setRootObject:self];
    if ([theConnection registerName:@"mhSprite"] == NO) {
      NSLog(@"Failed to register name mhSprite\n");
    }
    [theConnection retain];
    [theConnection setDelegate:self];
    
    NSToolbar *toolbar = [[NSToolbar alloc] initWithIdentifier:@"spriteToolbar"];
    [toolbar setDelegate:self];
    [toolbar setAllowsUserCustomization:NO];
    [toolbar setAutosavesConfiguration:YES];
    [spriteWindow setToolbar:toolbar];
    
    [qScriptBox setToolTip:@"Supported Actions (CaSe InSeNsItIvE):\n\nGoToChapter(chapter name); GoToTime(movie time); GoToPage(page number); StartPlaying; SetRate(rate); StepForward; StepBackward; GoToBeginning; GoToEnd; SaveTime; GoToSavedTime;\n\nTrackNamed(track name).SetEnabled(yes, no, true, false); TrackAtIndex(index number).SetEnabled(yes, no, true, false);\n\nSetMovieLanguage(language); SetVolume(-256->256);\n\nPreceed with OnFrameLoad. OnMouseEnter. or OnMouseExit. if desired"];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(flagsChanged:) name:@"flagsChangedNotification" object:nil];
}

- (NSToolbarItem *)toolbar:(NSToolbar *)tb itemForItemIdentifier:(NSString *)tbit willBeInsertedIntoToolbar:(BOOL)flag
{
    NSToolbarItem *tbi;
    NSArray *idents = [NSArray arrayWithObjects:@"New Sprite", @"New Page", @"Delete", @"Duplicate", @"Inspector", @"Options", @"Wizard", @"Update Movie", nil];
    void *actions[] = { @selector(newSprite:) , @selector(newPage:) , @selector(deletePage:) , @selector(duplicate:), @selector(makeKeyAndOrderFront:), @selector(more:), @selector(wizard:), @selector(updateMovie:) };
    
    int i, n = [idents count];
    for (i=0;i<n;i++) {
        if ([tbit isEqualToString:[idents objectAtIndex:i]]) {
            tbi = [[NSToolbarItem alloc] initWithItemIdentifier:tbit];
            [tbi setAction:actions[i]];
            [tbi setImage:[NSImage imageNamed:tbit]];
            if (i == 4) { // Inspector
                [tbi setTarget:inspectorWindow];
            } else {
                [tbi setTarget:self];
            }
            [tbi setLabel:tbit];
            [tbi setPaletteLabel:tbit];
            [tbi autorelease];
            return tbi;
        }
    }
    return nil;
}

- (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar *)toolbar
{
    return [NSArray arrayWithObjects:@"New Sprite", @"New Page", @"Duplicate", @"Delete", @"Inspector", @"Wizard", @"Update Movie", @"Options", nil];
}

- (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar *)toolbar
{
    return [NSArray arrayWithObjects:@"New Sprite", @"New Page", @"Delete", @"Duplicate", @"Inspector", @"Options", @"Wizard", @"Update Movie", NSToolbarSeparatorItemIdentifier, NSToolbarSpaceItemIdentifier, NSToolbarFlexibleSpaceItemIdentifier, NSToolbarCustomizeToolbarItemIdentifier, nil];
}

- (IBAction)customizeToolbar:(id)sender
{
    if (![[spriteWindow toolbar] customizationPaletteIsRunning])
        [[spriteWindow toolbar] runCustomizationPalette:nil];
}


- (void)open
{
    [spriteMovieView stop:nil];
    [spriteWindow setFrame:[self rightSize] display:YES];
    NSColor *color;
    NSData *aData = [[NSUserDefaults standardUserDefaults] objectForKey:@"spriteBGColor"];
    if (!aData) color = [NSColor whiteColor];
    else color = [NSUnarchiver unarchiveObjectWithData:aData];
    [bgColor setColor:color];
    [self setColor:color];
    MHMovie *aMovie = (MHMovie *)[spriteMovieView movie];
    NSMutableDictionary *menu = [aMovie menu];
    if (!menu)
        menu = [self newMenu];
    NSMutableArray *tracks = [menu objectForKey:@"tracks"];
    if (![tracks count])
        [self newPage:nil];
    tracks = [menu objectForKey:@"tracks"];
    [NSThread detachNewThreadSelector:@selector(reloadImages:) toTarget:self withObject:tracks];
    [spriteWindow makeKeyAndOrderFront:nil];
}

- (void)reloadImages:(NSArray *)tracks
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    NSArray *values = [NSArray arrayWithObjects:@"Image", @"Rollover", @"Click", nil];
    NSArray *keys = [NSArray arrayWithObjects:@"image", @"rimage", @"cimage", nil];
    NSImage *anImage;
    int height = 0;
    BOOL hasImage = NO;
    int i, j, k;
    for (i=0;i<[tracks count];i++) {
        [loadLock lock];
        NSArray *sprites = [[tracks objectAtIndex:i] objectForKey:@"sprites"];
        NSString *val = [[tracks objectAtIndex:i] objectForKey:@"bgImageSource"];
        anImage = nil;
        hasImage = ([[tracks objectAtIndex:i] objectForKey:@"bgImage"] != nil);
        [loadLock unlock];
        if (val && !hasImage) {
            if ([val hasPrefix:@"/"])
                anImage = [[NSImage alloc] initWithContentsOfFile:val];
            else
                anImage = [self imageFromFrame:[val intValue] height:[spriteMovieView sizeForMagnification:1.0].height - 16];
            [loadLock lock];
            if (anImage)
                [(NSMutableDictionary *)[tracks objectAtIndex:i] setObject:anImage forKey:@"bgImage"];
            [loadLock unlock];
        }
        for (j=0;j<[sprites count];j++) {
            [loadLock lock];
            NSMutableDictionary *sprite = [sprites objectAtIndex:j];
            height = [[sprite objectForKey:@"Height"] intValue];
            [loadLock unlock];
            for (k=0;k<3;k++) {
                anImage = nil;
                [loadLock lock];
                val = [sprite objectForKey:[values objectAtIndex:k]];
                BOOL hasImage = ([sprite objectForKey:[keys objectAtIndex:k]] != nil);
                [loadLock unlock];
                if (val && !hasImage) {
                    anImage = [self imageForSprite:sprite key:[values objectAtIndex:k]];
                    [loadLock lock];
                    if (anImage)
                        [sprite setObject:anImage forKey:[keys objectAtIndex:k]];
                    [loadLock unlock];
                }
            }
        }
        [theImageView setNeedsDisplay:YES];
    }
    [pool release];
}

- (void)setMovie:(NSMovie *)aMovie
{
    [spriteMovieView setMovie:aMovie];
    [theOutline reloadData];
    [theImageView setNeedsDisplay:YES];
}

- (NSRect)rightSize
{
    MHMovie *aMovie = (MHMovie *)[spriteMovieView movie];
    NSRect frame = [spriteWindow frame];
    if (!aMovie) return frame;
    Movie myMovie = [aMovie QTMovie];
    Track videoTrack = GetMovieIndTrackType(myMovie, 1, VideoMediaType, movieTrackMediaType);
    Fixed w, h;
    GetTrackDimensions(videoTrack, &w, &h);
    Rect box;
    box.top = box.left = 0;
    box.right = Fix2Long(w);
    box.bottom = Fix2Long(h);
    MatrixRecord m;
    GetTrackMatrix(videoTrack, &m);
    TransformRect(&m, &box, NULL);
    naturalSize.height = box.bottom - box.top;
    naturalSize.width = box.right - box.left;
    frame.size.height = naturalSize.height + 213 + (frame.size.height - [[spriteWindow contentView] frame].size.height);
    frame.size.width = naturalSize.width + 40;
    NSRect sFrame = [[spriteWindow screen] frame];
    if (frame.size.height > sFrame.size.height - 20) frame.size.height = sFrame.size.height - 20;
    if (frame.size.width > sFrame.size.width - 20) frame.size.width = sFrame.size.width - 20;
    //[spriteWindow setFrame:frame display:YES];
    return frame;
}

- (void)iSetX:(int)x Y:(int)y
{
    //[xField setIntValue:x];
    //[yField setIntValue:y];
}

// OutlineView Use
- (id)outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item
{
    NSMutableDictionary *menu = [[spriteMovieView movie] menu];
    if (!menu)
        menu = [self newMenu];
    if (!item) {
        if ([(NSArray *)[menu objectForKey:@"tracks"] count] > index)
            return [[menu objectForKey:@"tracks"] objectAtIndex:index];
        else
            return nil;
    }
    if ([(NSArray *)[item objectForKey:@"sprites"] count] > index)
        return [[item objectForKey:@"sprites"] objectAtIndex:index];
    return nil;
}

- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item 	{	return ![[item objectForKey:@"isLeaf"] boolValue];	}

- (int)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
{
    NSMutableDictionary *menu = [[spriteMovieView movie] menu];
    if (!menu)
        menu = [self newMenu];
    if (!item)
        return [(NSArray *)[menu objectForKey:@"tracks"] count];
    int i = [(NSArray *)[item objectForKey:@"sprites"] count];
    return i;
}

- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item
{
    NSString *idString = [tableColumn identifier];
    NSString *s = [item objectForKey:idString];
    return s;
}

- (BOOL)outlineView:(NSOutlineView *)outlineView shouldEditTableColumn:(NSTableColumn *)tableColumn item:(id)item
{
    //NSString *idString = [tableColumn identifier];
    //if ([idString isEqualToString:@"Name"]) return NO;
    //if ([item isKindOfClass:[oSprite class]] && [item added]) return NO;
    //if ([item shouldEditKey:idString]) return YES;
    //return NO;
    return YES;
}

- (void)outlineViewSelectionDidChange:(NSNotification *)notification
{
    [theImageView setNeedsDisplay:YES];
    [self updateWidgets:nil];
    [self syncWidgets:(NSMutableDictionary *)[theOutline selectedItem]];
}

//- (BOOL)outlineView:(NSOutlineView *)outlineView shouldSelectItem:(id)item;
//- (void)selectItem:(id)item;
//- (BOOL)outlineView:(NSOutlineView *)outlineView shouldExpandItem:(id)item;
//- (void)expandItem:(id)item;
- (void)outlineView:(NSOutlineView *)outlineView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn byItem:(id)item
{
    //NSArray *array = [NSArray arrayWithObjects:@"X", @"Y", @"Width", @"Height", nil];
    NSString *idString = [tableColumn identifier];
    NSArray *keys = [NSArray arrayWithObjects:@"Image", @"Rollover", @"Clicked", nil];
    NSArray *keys2 = [NSArray arrayWithObjects:@"image", @"rimage", @"cimage", nil];
    int index = [keys indexOfObject:idString];
    //int i, n = [array count];
    //for (i=0;i<n;i++) {
    //    if ([idString isEqualToString:[array objectAtIndex:i]])
    //        object = [NSNumber numberWithInt:[object intValue]];
    //}
    if ([object isKindOfClass:[NSString class]] && [object isEqualToString:@""]) {
        [item removeObjectForKey:idString];
        if (index != NSNotFound) [item removeObjectForKey:[keys2 objectAtIndex:index]];
    } else {
        [(NSMutableDictionary *)item setObject:object forKey:idString];
        if (index != NSNotFound) {
            NSImage *image = [[[NSImage alloc] initWithContentsOfFile:object] autorelease];
            if (image)
                [(NSMutableDictionary *)item setObject:image forKey:[keys2 objectAtIndex:index]];
            else
                [(NSMutableDictionary *)item removeObjectForKey:[keys2 objectAtIndex:index]];
        }
    }
    [theImageView setNeedsDisplay:YES];
}

- (void)outlineView:(NSOutlineView *)oview rightClickWithEvent:(NSEvent *)event
{
    NSPoint point = [event locationInWindow];
    point = [oview convertPoint:point fromView:nil];
    int row = [oview rowAtPoint:point];
    NSMutableDictionary *item = [oview itemAtRow:row];
    if (!item) return;
    [self selectItem:item];
    NSMenu *menu = [self rightClickMenu];
    if (menu)
        [NSMenu popUpContextMenu:menu withEvent:event forView:oview];
}

- (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
{
/*
    if ([item isKindOfClass:[oSprite class]] && [item added])
        [cell setTextColor:[NSColor grayColor]];
    else
        [cell setTextColor:[NSColor blackColor]];
        */
}

@end
