/* Copyright (c) 2004-2007, Marcus Müller . 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 Mulle kybernetiK 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. */ #include "common.h" #include "AppController.h" #import #include "SelectiveView.h" #include "NSView+Extensions.h" #include "NSImage+Extensions.h" #define NO_CAM_TITLE \ NSLocalizedString(@"No camera found?", "") #define NO_CAM_MSG \ NSLocalizedString(@"Quicktime returned some wicked error number (%d) " \ @"that I'm currently unable to interpret fully - " \ @"it's either another program is using the camera " \ @"already or you don't have a camera attached to " \ @"the computer. I'm really sorry I can't tell any " \ @"better. Sigh. Anyways, I'll quit now. " \ @"Just felt like I had to tell you. " \ , "") #define NO_CAM_BUTTON \ NSLocalizedString(@"Dammit!", "") #define CAM_MALFUNC_TITLE \ NSLocalizedString(@"Camera detached", "") #define CAM_MALFUNC_MSG \ NSLocalizedString(@"It looks like the camera has been removed from " \ @"the computer. The program will quit now." \ , "") @interface AppController (PrivateAPI) - (void)setup; - (BOOL)videoViewUsesImageAsSource; - (void)rebuildAppleScriptMenu; - (void)rebuildQtzEffectMenu; - (void)addScriptsAtPath:(NSString *)_path; - (void)addEffectsAtPath:(NSString *)_path; - (NSArray *)getResourcesOfTypes:(NSArray *)_types atPath:(NSString *)_path; - (void)addScripts:(NSArray *)_scripts atPath:(NSString *)_path; - (void)addEffects:(NSArray *)_effects atPath:(NSString *)_path; - (void)addItems:(NSArray *)_items withBasePath:(NSString *)_path toMenu:(NSMenu *)_menu usingPrototype:(NSMenuItem *)_prototype; - (NSString *)qtzEffectPathForNone; - (void)reorderAndSetQtzEffects; - (void)activateSelectedAppleScriptFromDefaults; - (void)activateSelectedQtzEffectsFromDefaults; - (void)activateMenuItemInMenu:(NSMenu *)_menu forDefaultKey:(NSString *)_dk; - (void)setSelectedMenuItem:(NSMenuItem *)_item; - (BOOL)shouldExecuteScript; - (void)executeScriptAtPath:(NSString *)_path; - (void)presentErrorWithCode:(NSNumber *)_ec; - (void)setResolution:(NSSize)_s; - (void)placeImagesOnPasteboard; - (void)placeImageOnPasteboard:(NSImage *)_retainedImage; - (void)captureImages; @end NSString *MSCameraDidFailNotification = @"MSCameraDidFail"; @implementation AppController static NSSize res320x240; static NSSize res640x480; static float windowHeightOffset = 0; static float windowSizeOffset = 0; + (id)sharedController { return [NSApp delegate]; } - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; [self->qtzEffects release]; [self->selectedQtzEffects release]; [self->selectedScriptPath release]; [super dealloc]; } - (void)awakeFromNib { NSUserDefaults *ud; res320x240 = NSMakeSize(320, 240); res640x480 = NSMakeSize(640, 480); windowHeightOffset = ([self->window frame].size.height - [self->videoView frame].size.height); ud = [NSUserDefaults standardUserDefaults]; if ([ud boolForKey:@"ShouldScaleImage"]) [self toggleShouldScaleImage:self]; if ([ud boolForKey:@"ShouldFlipImage"]) [self toggleShouldFlipImage:self]; self->flags.keepOriginalImage = [ud boolForKey:@"KeepOriginalImage"]; [self setup]; } - (void)setup { NSNotificationCenter *nc; NSUserDefaults *ud; OSErr err; int tag; self->qtzEffects = [[NSMutableArray alloc] initWithCapacity:8]; self->selectedQtzEffects = [[NSMutableArray alloc] initWithCapacity:8]; [self rebuildAppleScriptMenu]; [self rebuildQtzEffectMenu]; nc = [NSNotificationCenter defaultCenter]; [nc addObserver:self selector:@selector(cameraDidFail:) name:MSCameraDidFailNotification object:nil]; ud = [NSUserDefaults standardUserDefaults]; tag = [ud integerForKey:@"VideoResolutionTag"]; if (tag != 0) { NSMenuItem *item; item = [self->videoMenu itemWithTag:tag]; if (item) [self performSelector:[item action] withObject:item]; } #if 0 /* now lets create a window and display the video data passed to us by the sequence grabber */ err = [self->videoView doSeqGrab:[self->videoView bounds]]; /* put up an error dialog to display any errors */ if(err != noErr) { [self performSelector:@selector(presentErrorWithCode:) withObject:[NSNumber numberWithInt:err] afterDelay:0.1]; } #else // [self->videoView live:self]; #endif } - (void)rebuildAppleScriptMenu { int i; unsigned count; NSArray *directories; count = [self->appleScriptMenu numberOfItems]; /* flush all contents if any */ if (count > 1) { for (i = count - 1; i >= 1; i--) { [self->appleScriptMenu removeItemAtIndex:i]; } } /* scan all relevant directories */ [self addScriptsAtPath:[[NSBundle mainBundle] resourcePath]]; directories = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES); count = [directories count]; for (i = 0; i < count; i++) { NSString *path; path = [directories objectAtIndex:i]; path = [path stringByAppendingPathComponent:@"MulleSight"]; [self addScriptsAtPath:path]; } [self activateSelectedAppleScriptFromDefaults]; } - (void)rebuildQtzEffectMenu { int i; unsigned count; NSArray *directories; [self->qtzPrototypeMenuItem setRepresentedObject:[self qtzEffectPathForNone]]; count = [self->qtzEffectMenu numberOfItems]; /* flush all contents if any */ if (count > 2) { for (i = count - 1; i >= 2; i--) { [self->qtzEffectMenu removeItemAtIndex:i]; } } /* scan all relevant directories */ [self addEffectsAtPath:[[NSBundle mainBundle] resourcePath]]; directories = \ NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES); count = [directories count]; for (i = 0; i < count; i++) { NSString *path; path = [directories objectAtIndex:i]; path = [path stringByAppendingPathComponent:@"MulleSight"]; [self addEffectsAtPath:path]; } [self->qtzEffects sortUsingSelector:@selector(compareLastPathComponent:)]; [self activateSelectedQtzEffectsFromDefaults]; } - (void)addScriptsAtPath:(NSString *)_path { static NSArray *exts = nil; NSArray *items; if (!exts) exts = [[NSArray alloc] initWithObjects:@"applescript", @"scpt", nil]; items = [self getResourcesOfTypes:exts atPath:_path]; [self addScripts:items atPath:_path]; } - (void)addEffectsAtPath:(NSString *)_path { static NSArray *exts = nil; NSArray *items; unsigned i, count; if (!exts) exts = [[NSArray alloc] initWithObjects:@"qtz", nil]; items = [self getResourcesOfTypes:exts atPath:_path]; [self addEffects:items atPath:_path]; for (i = 0, count = [items count]; i < count; i++) { NSString *qtzPath; qtzPath = [items objectAtIndex:i]; if ([qtzPath hasPrefix:@"_"]) continue; /* skip internal effects */ qtzPath = [_path stringByAppendingPathComponent:qtzPath]; [self->qtzEffects addObject:qtzPath]; } } - (NSArray *)getResourcesOfTypes:(NSArray *)_types atPath:(NSString *)_path { NSArray *candidates; candidates = [[NSFileManager defaultManager] directoryContentsAtPath:_path]; return [candidates pathsMatchingExtensions:_types]; } - (void)addScripts:(NSArray *)_scripts atPath:(NSString *)_path { [self addItems:_scripts withBasePath:_path toMenu:self->appleScriptMenu usingPrototype:self->prototypeMenuItem]; } - (NSString *)qtzEffectPathForNone { return [[NSBundle mainBundle] pathForResource:@"_Pre" ofType:@"qtz"]; } - (void)addEffects:(NSArray *)_effects atPath:(NSString *)_path { [self addItems:_effects withBasePath:_path toMenu:self->qtzEffectMenu usingPrototype:self->qtzPrototypeMenuItem]; } - (void)addItems:(NSArray *)_items withBasePath:(NSString *)_path toMenu:(NSMenu *)_menu usingPrototype:(NSMenuItem *)_prototype { NSBundle *bundle; unsigned i, count; count = [_items count]; if (count > 0) { [_menu addItem:[NSMenuItem separatorItem]]; bundle = [NSBundle mainBundle]; } for (i = 0; i < count; i++) { NSString *name, *completePath; NSMenuItem *item; name = [_items objectAtIndex:i]; // skip "internal" effects if ([name hasPrefix:@"_"]) continue; completePath = [_path stringByAppendingPathComponent:name]; name = [name stringByDeletingPathExtension]; name = [bundle localizedStringForKey:name value:nil table:nil]; item = [_prototype copy]; [item setTitle:name]; [item setRepresentedObject:completePath]; [_menu addItem:item]; } } - (void)activateSelectedQtzEffectsFromDefaults { NSUserDefaults *ud; id paths; NSString *path; unsigned i, count; BOOL scanMenu; ud = [NSUserDefaults standardUserDefaults]; paths = [ud objectForKey:@"SelectedQtzEffectPaths"]; if (paths != nil && [paths isKindOfClass:[NSArray class]]) { if ([paths count] <= 1) { scanMenu = YES; path = [paths lastObject]; } else { scanMenu = NO; } } else { scanMenu = YES; path = paths; } if (scanMenu) { NSMenu *menu; if (path == nil) path = [self qtzEffectPathForNone]; menu = self->qtzEffectMenu; for (i = 0, count = [menu numberOfItems]; i < count; i++) { id item; item = [menu itemAtIndex:i]; if ([[item representedObject] isEqualToString:path]) { [self selectQtzEffect:item]; return; } } // Edge case: default doesn't exist (anymore?) NSLog(@"WARNING: Didn't find SelectedQtzEffectPaths '%@', resetting.", paths); [self selectQtzEffect:[menu itemAtIndex:0]]; } else { /* multiple */ [self->selectedQtzEffects removeAllObjects]; [self->selectedQtzEffects addObjectsFromArray:paths]; /* remove duplicates, then append unused effects */ [self->qtzEffects removeObjectsInArray:paths]; paths = [paths arrayByAddingObjectsFromArray:self->qtzEffects]; [self->qtzEffects removeAllObjects]; [self->qtzEffects addObjectsFromArray:paths]; /* render effects */ [self->videoView setQtzFilePaths:self->selectedQtzEffects]; } } - (void)activateSelectedAppleScriptFromDefaults { [self activateMenuItemInMenu:self->appleScriptMenu forDefaultKey:@"SelectedAppleScriptPath"]; } - (void)activateMenuItemInMenu:(NSMenu *)_menu forDefaultKey:(NSString *)_dk { NSUserDefaults *ud; NSString *path; id item; unsigned i, count; ud = [NSUserDefaults standardUserDefaults]; path = [ud objectForKey:_dk]; for (i = 0, count = [_menu numberOfItems]; i < count; i++) { item = [_menu itemAtIndex:i]; if ([[item representedObject] isEqualToString:path]) { [self performSelector:[item action] withObject:item]; return; } } // Edge case: default doesn't exist (anymore?) NSLog(@"WARNING: Didn't find %@ '%@', resetting.", _dk, path); item = [_menu itemAtIndex:0]; [self performSelector:[item action] withObject:item]; } - (void)applicationDidFinishLaunching:(NSNotification *)_notif { } - (void)applicationWillTerminate:(NSNotification *)_notif { #if 0 [self->videoView endGrab]; #endif } - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)_app { return YES; } /* error handling */ - (void)cameraDidFail:(NSNotification *)_notif { [self presentErrorWithCode:[_notif object]]; } - (void)presentErrorWithCode:(NSNumber *)_ec { id panel; NSString *title, *msg; int ec; /* -9405 (couldntGetRequiredComponent) -> camera in use? -2211 -> camera disconnected? */ ec = [_ec intValue]; switch(ec) { case -2211: title = CAM_MALFUNC_TITLE; msg = CAM_MALFUNC_MSG; break; default: title = NO_CAM_TITLE; msg = [NSString stringWithFormat:NO_CAM_MSG, ec]; break; } panel = NSGetCriticalAlertPanel(title, msg, NO_CAM_BUTTON, nil, nil); [NSApp beginSheet:panel modalForWindow:self->window modalDelegate:self didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) contextInfo:NULL]; [NSApp runModalForWindow:panel]; [panel orderOut:self]; NSReleaseAlertPanel(panel); [self->window close]; } - (void)sheetDidEnd:(NSWindow *)_sheet returnCode:(int)_rc contextInfo:(void *)_ctx { [NSApp stopModalWithCode:_rc]; } - (void)setResolution:(NSSize)_s { NSRect r; r = [self->window frame]; r.size = _s; r.size.width += windowSizeOffset; r.size.height += windowHeightOffset; [self->window setFrame:r display:YES]; } - (BOOL)videoViewUsesImageAsSource { return [self->videoView hasAuxiliaryInputImage]; } /* validation */ - (BOOL)validateMenuItem:(NSMenuItem *)_item { SEL action = [_item action]; if (action == @selector(toggleShouldScaleImage:)) [_item setState:self->flags.shouldScaleImage ? NSOnState : NSOffState]; else if (action == @selector(toggleShouldFlipImage:)) [_item setState:self->flags.shouldFlipImage ? NSOnState : NSOffState]; else if (action == @selector(selectAppleScript:)) [_item setState:(self->selectedMenuItem == _item) ? NSOnState : NSOffState]; else if (action == @selector(selectQtzEffect:)) { NSArray *paths; paths = [self->videoView qtzFilePaths]; if ([paths count] == 1) { NSString *path; path = [paths lastObject]; [_item setState:([_item representedObject] == path) ? NSOnState : NSOffState]; } else if ([paths count] == 0) { NSString *path; BOOL isEqual; path = [self qtzEffectPathForNone]; isEqual = [[_item representedObject] isEqualToString:path]; [_item setState:isEqual ? NSOnState : NSOffState]; } else { [_item setState:NSOffState]; } } else if (action == @selector(selectMultipleQtzEffects:)) { unsigned numEffects; numEffects = [[self->videoView qtzFilePaths] count]; [_item setState:(numEffects > 1) ? NSOnState : NSOffState]; } else if (action == @selector(adjustResolution:)) { int tag; NSSize s; tag = [_item tag]; s = [self->videoView frame].size; if(((tag == 1000) && NSEqualSizes(s, res320x240)) || ((tag == 1001) && NSEqualSizes(s, res640x480))) { [_item setState:NSOnState]; return NO; } else { [_item setState:NSOffState]; return YES; } } else if (action == @selector(selectCameraAsVideoSource:)) [_item setState:[self videoViewUsesImageAsSource] ? NSOffState : NSOnState]; else if (action == @selector(selectImageAsVideoSource:)) { [_item setState:[self videoViewUsesImageAsSource] && !self->flags.auxIsArbitrary ? NSOnState : NSOffState]; } else if (action == @selector(selectImageFromDialogAsVideoSource:)) { [_item setState:[self videoViewUsesImageAsSource] && self->flags.auxIsArbitrary ? NSOnState : NSOffState]; } return YES; } /* actions */ - (IBAction)adjustResolution:(id)sender { int tag; tag = [sender tag]; switch (tag) { case 1000: [self setResolution:res320x240]; break; default: [self setResolution:res640x480]; break; } [[NSUserDefaults standardUserDefaults] setInteger:tag forKey:@"VideoResolutionTag"]; } - (IBAction)selectAppleScript:(id)sender { NSUserDefaults *ud; NSString *path; ud = [NSUserDefaults standardUserDefaults]; path = [sender representedObject]; if (path) [ud setObject:path forKey:@"SelectedAppleScriptPath"]; else [ud removeObjectForKey:@"SelectedAppleScriptPath"]; self->selectedMenuItem = sender; self->selectedScriptPath = path; [self->captureOptionsControl setEnabled:path ? YES : NO forSegment:2]; [self->captureOptionsControl setSelected:path ? YES : NO forSegment:2]; self->flags.ignoreExecuteScript = NO; } - (IBAction)selectQtzEffect:(id)sender { NSUserDefaults *ud; NSString *path; NSArray *paths; ud = [NSUserDefaults standardUserDefaults]; path = [sender representedObject]; if (![path isEqualToString:[self qtzEffectPathForNone]]) { paths = [NSArray arrayWithObject:path]; [ud setObject:paths forKey:@"SelectedQtzEffectPaths"]; } else { paths = [NSArray array]; [ud removeObjectForKey:@"SelectedQtzEffectPaths"]; } [self->videoView setQtzFilePaths:paths]; /* just in case */ [self->selectedQtzEffects removeAllObjects]; [self->selectedQtzEffects addObjectsFromArray:paths]; [[self->multipleEffectsTableView window] orderOut:self]; } - (IBAction)selectMultipleQtzEffects:(id)sender { NSArray *paths; if (!self->multipleEffectsTableView) { NSArray *types; [NSBundle loadNibNamed:@"MultipleEffectsSelection" owner:self]; types = [NSArray arrayWithObject:@"QtzEffectPathType"]; [self->multipleEffectsTableView registerForDraggedTypes:types]; } if (sender != self) { [[self->multipleEffectsTableView window] makeKeyAndOrderFront:self]; [self->multipleEffectsTableView reloadData]; [self->multipleEffectsTableView deselectAll:self]; } } - (IBAction)toggleQtzEffect:(id)sender { int rowIdx; NSString *path; BOOL shouldRemove; rowIdx = [self->multipleEffectsTableView selectedRow]; path = [self->qtzEffects objectAtIndex:rowIdx]; shouldRemove = [self->selectedQtzEffects containsObject:path]; if (shouldRemove) [self->selectedQtzEffects removeObject:path]; else [self->selectedQtzEffects addObject:path]; [self reorderAndSetQtzEffects]; } - (IBAction)toggleShouldScaleImage:(id)sender { BOOL shouldScale; shouldScale = ![self isShouldScaleImage]; [self setShouldScaleImage:shouldScale]; [self->captureOptionsControl setEnabled:shouldScale forSegment:1]; [self->captureOptionsControl setSelected:shouldScale forSegment:1]; self->flags.ignoreShouldScaleImage = NO; } - (IBAction)toggleShouldFlipImage:(id)sender { [self setShouldFlipImage:![self isShouldFlipImage]]; } - (IBAction)copy:(id)sender { [self->captureButton performClick:sender]; } - (IBAction)captureImage:(id)sender { static NSSound *shutterSound = nil; if (!shutterSound) shutterSound = [[NSSound soundNamed:@"CloseShutter"] retain]; [self captureImages]; [self placeImagesOnPasteboard]; [shutterSound play]; if (![self videoViewUsesImageAsSource]) [self selectImageAsVideoSource:self]; if ([self shouldExecuteScript]) [self executeScriptAtPath:self->selectedScriptPath]; } - (IBAction)selectCameraAsVideoSource:(id)sender { [self->videoView setAuxiliaryInputImage:nil]; [self->videoSourceControl selectSegmentWithTag:1 /* camera */]; } - (IBAction)selectImageAsVideoSource:(id)sender { [self->videoView setAuxiliaryInputImage:[self lastCapturedOriginalImage]]; self->flags.auxIsArbitrary = NO; if (!self->lastCapturedOriginalImage) [self->videoSourceControl selectSegmentWithTag:1 /* camera */]; else [self->videoSourceControl selectSegmentWithTag:0 /* image */]; } - (IBAction)selectImageFromDialogAsVideoSource:(id)sender { NSOpenPanel *panel; panel = [NSOpenPanel openPanel]; [panel setCanChooseDirectories:NO]; [panel setCanChooseFiles:YES]; [panel setAllowsMultipleSelection:NO]; if ([panel runModalForDirectory:nil file:nil] == NSOKButton) { NSString *path; NSImage *img; path = [[panel filenames] lastObject]; img = [[NSImage alloc] initWithContentsOfFile:path]; if (!img) { NSBeep(); return; } [self->videoView setAuxiliaryInputImage:img]; [img release]; self->flags.auxIsArbitrary = YES; [self->videoSourceControl selectSegmentWithTag:0 /* image */]; } else { NSBeep(); } } - (IBAction)selectVideoSource:(id)sender { int tag; tag = [sender selectedSegment]; /* not really a tag, but will do */ if (tag == 1 /* camera */) [self selectCameraAsVideoSource:sender]; else [self selectImageAsVideoSource:sender]; } - (IBAction)selectCaptureOption:(id)sender { int idx; idx = [sender selectedSegment]; if (idx == 0 /* selection */) [self->videoView setUseSelectionRect:[sender isSelectedForSegment:idx]]; else if (idx == 1 /* scale */) self->flags.ignoreShouldScaleImage = ![sender isSelectedForSegment:idx]; else /* script */ self->flags.ignoreExecuteScript = ![sender isSelectedForSegment:idx]; } /* About Panel */ - (IBAction)orderFrontAboutPanel:(id)sender { if (!self->aboutPanel) { NSBundle *b; NSString *s, *path; NSData *rtfData; NSRange r; [NSBundle loadNibNamed:@"InfoPanel" owner:self]; NSAssert(self->aboutPanel != nil, @"Couldn't load InfoPanel.nib!"); b = [NSBundle mainBundle]; path = [b pathForResource:@"_InfoPanel" ofType:@"qtz"]; [self->aboutQCView loadCompositionFromFile:path]; s = [b objectForInfoDictionaryKey:@"CFBundleShortVersionString"]; s = [NSString stringWithFormat:@"v%@", s]; [self->aboutQCView setValue:s forInputKey:@"version"]; path = [b pathForResource:@"Credits" ofType:@"rtf"]; NSAssert(path != nil, @"Couldn't find Credits.rtf!"); rtfData = [NSData dataWithContentsOfFile:path]; r = NSMakeRange(0, [[self->aboutCreditsTextView string] length]); [self->aboutCreditsTextView replaceCharactersInRange:r withRTF:rtfData]; s = [b objectForInfoDictionaryKey:@"NSHumanReadableCopyright"]; [self->aboutCopyrightTextField setStringValue:s]; } [self->aboutPanel center]; [self->aboutPanel makeKeyAndOrderFront:self]; } /* actions on images */ - (void)placeImageOnPasteboard:(NSImage *)_retainedImage { NSPasteboard *pboard; pboard = [NSPasteboard generalPasteboard]; [pboard declareTypes:[NSArray arrayWithObject:NSTIFFPboardType] owner:self]; [pboard setData:[_retainedImage TIFFRepresentation] forType:NSTIFFPboardType]; [_retainedImage release]; } - (void)placeImagesOnPasteboard { NSImage *img; if (self->flags.keepOriginalImage) { img = [self->lastCapturedOriginalImage copy]; [self performSelector:@selector(placeImageOnPasteboard:) withObject:img afterDelay:0.0]; img = [self->lastCapturedImage copy]; [self performSelector:@selector(placeImageOnPasteboard:) withObject:img afterDelay:1.0]; } else { img = [self->lastCapturedImage copy]; [self placeImageOnPasteboard:img]; } } - (void)executeScriptAtPath:(NSString *)_path { NSAppleScript *script; NSAppleEventDescriptor *desc; NSDictionary *errorInfo; NSString *source; source = [NSString stringWithContentsOfFile:_path]; script = [[NSAppleScript alloc] initWithSource:source]; desc = [script executeAndReturnError:&errorInfo]; [script release]; } - (BOOL)shouldExecuteScript { if (self->flags.ignoreExecuteScript) return NO; return self->selectedScriptPath ? YES : NO; } /* generating images */ - (void)captureImages { [self->lastCapturedOriginalImage release]; self->lastCapturedOriginalImage = [[self originalImage] copy]; [self->lastCapturedImage release]; self->lastCapturedImage = [[self image] copy]; } - (NSImage *)originalImage { return [self->videoView originalImage]; } - (NSImage *)lastCapturedOriginalImage { if (!self->lastCapturedOriginalImage) { NSPasteboard *pboard; pboard = [NSPasteboard generalPasteboard]; if ([NSImage canInitWithPasteboard:pboard]) { self->lastCapturedOriginalImage = [[NSImage alloc] initWithPasteboard:pboard]; } } return self->lastCapturedOriginalImage; } - (NSImage *)image { NSImage *img; img = [self->videoView image]; // TODO: scaling should be configurable if (self->flags.shouldScaleImage && !self->flags.ignoreShouldScaleImage) img = [img scaledImageWithSize:NSMakeSize(64, 64)]; return img; } - (NSData *)originalImageData { return [[self originalImage] TIFFRepresentation]; } - (NSData *)imageData { return [[self image] TIFFRepresentation]; } /* QTZ Effects */ - (void)reorderAndSetQtzEffects { unsigned i, count; NSArray *currentEffects; NSUserDefaults *ud; currentEffects = [self->selectedQtzEffects copy]; [self->selectedQtzEffects removeAllObjects]; for (i = 0, count = [self->qtzEffects count]; i < count; i++) { NSString *path; path = [self->qtzEffects objectAtIndex:i]; if ([currentEffects containsObject:path]) [self->selectedQtzEffects addObject:path]; } [currentEffects release]; ud = [NSUserDefaults standardUserDefaults]; if ([self->selectedQtzEffects count]) [ud setObject:self->selectedQtzEffects forKey:@"SelectedQtzEffectPaths"]; else [ud removeObjectForKey:@"SelectedQtzEffectPaths"]; [self->videoView setQtzFilePaths:self->selectedQtzEffects]; } /* NSTableView DataSource */ - (int)numberOfRowsInTableView:(NSTableView *)_tv { return [self->qtzEffects count]; } - (id)tableView:(NSTableView *)_tv objectValueForTableColumn:(NSTableColumn *)_tc row:(int)_rowIdx { return [self->qtzEffects objectAtIndex:_rowIdx]; } - (BOOL)tableView:(NSTableView *)_tv writeRowsWithIndexes:(NSIndexSet *)_rowIdxs toPasteboard:(NSPasteboard*)_pboard { NSData *data; data = [NSArchiver archivedDataWithRootObject:_rowIdxs]; [_pboard declareTypes:[NSArray arrayWithObject:@"QtzEffectPathType"] owner:self]; [_pboard setData:data forType:@"QtzEffectPathType"]; return YES; } - (NSDragOperation)tableView:(NSTableView*)_tv validateDrop:(id )_info proposedRow:(int)_rowIdx proposedDropOperation:(NSTableViewDropOperation)_op { if (_op == NSTableViewDropOn) return NSDragOperationNone; return NSDragOperationMove; } - (BOOL)tableView:(NSTableView *)_tv acceptDrop:(id )_info row:(int)_rowIdx dropOperation:(NSTableViewDropOperation)_op { NSPasteboard *pboard; NSData *rowData; NSIndexSet *rowIdxs; int srcIdx; id obj; pboard = [_info draggingPasteboard]; rowData = [pboard dataForType:@"QtzEffectPathType"]; rowIdxs = [NSUnarchiver unarchiveObjectWithData:rowData]; srcIdx = [rowIdxs firstIndex]; if (srcIdx == _rowIdx) return NO; obj = [[self->qtzEffects objectAtIndex:srcIdx] retain]; if (srcIdx > _rowIdx) { [self->qtzEffects removeObjectAtIndex:srcIdx]; [self->qtzEffects insertObject:obj atIndex:_rowIdx]; } else { [self->qtzEffects insertObject:obj atIndex:_rowIdx]; [self->qtzEffects removeObjectAtIndex:srcIdx]; } [obj release]; [self->multipleEffectsTableView reloadData]; [self reorderAndSetQtzEffects]; return YES; } /* NSTableView Delegate */ - (void)tableView:(NSTableView *)_tv willDisplayCell:_cell forTableColumn:(NSTableColumn *)_tc row:(int)_rowIdx { NSString *path, *title; path = [self->qtzEffects objectAtIndex:_rowIdx]; title = [[path lastPathComponent] stringByDeletingPathExtension]; title = [[NSBundle mainBundle] localizedStringForKey:title value:nil table:nil]; [(NSButtonCell *)_cell setTitle:title]; [_cell setState:[self->selectedQtzEffects containsObject:path] ? NSOnState : NSOffState]; } @end /* AppController */ @implementation AppController (AppleScriptConvenience) - (void)setShouldScaleImage:(BOOL)_yn { NSUserDefaults *ud; ud = [NSUserDefaults standardUserDefaults]; self->flags.shouldScaleImage = _yn; [ud setBool:self->flags.shouldScaleImage forKey:@"ShouldScaleImage"]; } - (BOOL)isShouldScaleImage { return self->flags.shouldScaleImage; } - (void)setShouldFlipImage:(BOOL)_yn { NSUserDefaults *ud; ud = [NSUserDefaults standardUserDefaults]; self->flags.shouldFlipImage = _yn; [ud setBool:self->flags.shouldFlipImage forKey:@"ShouldFlipImage"]; // tell our view about it [self->videoView setShouldFlipImage:_yn]; } - (BOOL)isShouldFlipImage { return self->flags.shouldFlipImage; } - (void)setUseSelection:(BOOL)_yn { [self->videoView setUseSelectionRect:_yn]; } - (BOOL)isUseSelection { return [self->videoView useSelectionRect]; } @end /* AppController (AppleScriptConvenience) */ @implementation NSString (MSSortingConvenience) - (NSComparisonResult)compareLastPathComponent:(NSString *)_other { return [[self lastPathComponent] compare:[_other lastPathComponent]]; } @end /* NSString (MSSortingConvenience) */