Changeset 6365

Show
Ignore:
Timestamp:
11/29/08 16:33:13 (1 month ago)
Author:
morris
Message:

Add iPhone/iPod/AppleTV exporting

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/Spiral/English.lproj/Playlist.nib/classes.nib

    r5121 r6365  
    6262                                <key>browseInFullscreen</key> 
    6363                                <string>id</string> 
     64                                <key>destination</key> 
     65                                <string>id</string> 
     66                                <key>format</key> 
     67                                <string>id</string> 
    6468                                <key>fullscreen</key> 
     69                                <string>id</string> 
     70                                <key>newExportGroup</key> 
    6571                                <string>id</string> 
    6672                                <key>newGroup</key> 
     
    9399                                <key>_drillMovieView</key> 
    94100                                <string>QTMovieView</string> 
     101                                <key>_exportGroupDestinationPopUpButton</key> 
     102                                <string>NSPopUpButton</string> 
     103                                <key>_exportGroupDirectoryMenuItem</key> 
     104                                <string>NSMenuItem</string> 
     105                                <key>_exportGroupFormatPopUpButton</key> 
     106                                <string>NSPopUpButton</string> 
     107                                <key>_exportGroupNameTextField</key> 
     108                                <string>NSTextField</string> 
     109                                <key>_exportGroupPanel</key> 
     110                                <string>NSPanel</string> 
     111                                <key>_exportGroupSameAsOriginalMenuItem</key> 
     112                                <string>NSMenuItem</string> 
     113                                <key>_exportGroupiTunesMenuItem</key> 
     114                                <string>NSMenuItem</string> 
    95115                                <key>_filesTableColumn</key> 
    96116                                <string>NSTableColumn</string> 
  • trunk/Spiral/English.lproj/Playlist.nib/info.nib

    r6308 r6365  
    1212        <array> 
    1313                <integer>6</integer> 
     14                <integer>252</integer> 
     15                <integer>97</integer> 
    1416        </array> 
    1517        <key>IBSystem Version</key> 
  • trunk/Spiral/SPApplicationController.m

    r6359 r6365  
    3030#import "SPApplicationController.h" 
    3131#import "SPDrillController.h" 
     32#import "SPExportController.h" 
    3233#import "SPInspectorController.h" 
    3334#import "SPMobileRemote.h" 
     
    376377- (void)applicationWillResignActive:(NSNotification *)notification { 
    377378    [[SPAppleRemote sharedRemote] stopListening]; 
     379} 
     380 
     381 
     382 
     383- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender { 
     384        if([[SPExportController exportController] numberOfExports] > 0 || 
     385           [[SPPlaylistController playlistController] numberOfExports] > 0) { 
     386                return [(WIApplication *) NSApp runTerminationDelayPanelWithTimeInterval:30.0 message:NSLS(@"There are unfinished exports. Quitting now will terminate them.", @"Quit warning")]; 
     387        } 
     388 
     389        return NSTerminateNow; 
    378390} 
    379391 
     
    489501                delegate = [[NSApp keyWindow] delegate]; 
    490502                 
    491                 if([delegate isKindOfClass:[SPPlayerController class]]) 
    492                         return YES; 
    493                 else if([delegate isKindOfClass:[SPPlaylistController class]]) 
    494                         return ([[delegate selectedFiles] count] == 1); 
     503                return [delegate isKindOfClass:[SPPlayerController class]]; 
    495504        } 
    496505         
     
    787796 
    788797- (IBAction)export:(id)sender { 
    789         NSArray                 *files; 
    790         QTMovie                 *movie; 
    791         id                              delegate; 
     798        SPMovieController       *controller; 
     799        id                                      delegate; 
    792800         
    793801        delegate = [[NSApp keyWindow] delegate]; 
    794802         
    795803        if([delegate isKindOfClass:[SPPlayerController class]]) { 
    796                 movie = [[(SPPlayerController *) delegate movieController] movie]; 
    797                  
    798                 NSLog(@"export %@", movie); 
    799         } 
    800         else if([delegate isKindOfClass:[SPPlaylistController class]]) { 
    801                 files = [delegate selectedFiles]; 
    802                  
    803                 if([files count] == 1) 
    804                         NSLog(@"export %@", [files objectAtIndex:0]); 
     804                controller = [(SPPlayerController *) delegate movieController]; 
     805                 
     806                [[SPExportController exportController] beginSavePanelForWindow:[delegate window] 
     807                                                                                                                                 movie:[controller movie] 
     808                                                                                                                  playlistFile:[controller playlistFile]]; 
    805809        } 
    806810} 
  • trunk/Spiral/SPPlaylistController.h

    r6359 r6365  
    2727 */ 
    2828 
     29#import "SPExportJob.h" 
    2930#import "SPPlaylistLoader.h" 
    3031 
     
    3940@class SPFullscreenWindow, SPPlaylistGroup, SPPlaylistFile, SPPlaylistLoader; 
    4041 
    41 @interface SPPlaylistController : WIWindowController <SPPlaylistLoaderDelegate> { 
     42@interface SPPlaylistController : WIWindowController <SPPlaylistLoaderDelegate, SPExportJobDelegate> { 
    4243        IBOutlet WIOutlineView                          *_outlineView; 
    4344        IBOutlet NSTableColumn                          *_filesTableColumn; 
     
    6061        IBOutlet NSPredicateEditor                      *_predicateEditor; 
    6162         
     63        IBOutlet NSPanel                                        *_exportGroupPanel; 
     64        IBOutlet NSTextField                            *_exportGroupNameTextField; 
     65        IBOutlet NSPopUpButton                          *_exportGroupFormatPopUpButton; 
     66        IBOutlet NSPopUpButton                          *_exportGroupDestinationPopUpButton; 
     67        IBOutlet NSMenuItem                                     *_exportGroupSameAsOriginalMenuItem; 
     68        IBOutlet NSMenuItem                                     *_exportGroupiTunesMenuItem; 
     69        IBOutlet NSMenuItem                                     *_exportGroupDirectoryMenuItem; 
     70         
    6271        SPPlaylistGroup                                         *_playlist; 
    6372        SPPlaylistGroup                                         *_recentPlaylist; 
    6473         
    6574        SPPlaylistLoader                                        *_loader; 
     75         
     76        NSUInteger                                                      _exports; 
     77        NSMutableArray                                          *_exportQueue; 
    6678 
    6779        SPPlaylistRepeatMode                            _repeatMode; 
     
    8092- (void)setShuffle:(BOOL)shuffle; 
    8193- (BOOL)shuffle; 
     94- (NSUInteger)numberOfExports; 
    8295 
    8396- (void)openSelection; 
     
    90103- (IBAction)newGroup:(id)sender; 
    91104- (IBAction)newSmartGroup:(id)sender; 
     105- (IBAction)newExportGroup:(id)sender; 
     106- (IBAction)format:(id)sender; 
     107- (IBAction)destination:(id)sender; 
    92108- (IBAction)remove:(id)sender; 
    93109- (IBAction)revealInFinder:(id)sender; 
     
    98114- (IBAction)browseInFullscreen:(id)sender; 
    99115 
    100 - (NSArray *)selectedFiles; 
    101116- (SPPlaylistFile *)previousFileForFile:(SPPlaylistFile *)file; 
    102117- (SPPlaylistFile *)nextFileForFile:(SPPlaylistFile *)file; 
  • trunk/Spiral/SPPlaylistController.m

    r6359 r6365  
    5858- (void)_setShuffle:(BOOL)shuffle; 
    5959 
     60- (void)_setExportGroupDirectory:(NSString *)path; 
     61- (void)_startNextExportJob; 
     62 
    6063- (void)_validate; 
    6164 
     
    6669 
    6770+ (SPPlaylistGroup *)_defaultPlaylist { 
    68         SPPlaylistGroup         *playlist, *group; 
     71        SPPlaylistGroup                 *playlist, *group; 
     72        SPPlaylistExportGroup   *exportGroup; 
    6973         
    7074        playlist = [[SPPlaylistGroup alloc] initWithName:@"<SPPlaylist>"]; 
     
    7579         
    7680        [playlist addItem:group]; 
     81         
     82        exportGroup = [SPPlaylistExportGroup exportGroupWithName:NSLS(@"Export for iPhone", @"Playlist export group name")]; 
     83        [exportGroup setFormat:SPExportiPhone]; 
     84         
     85        [playlist addItem:exportGroup]; 
    7786         
    7887        return [playlist autorelease]; 
     
    132141 
    133142- (void)_reloadItem:(id)item { 
     143        NSString                *name, *path, *destinationPath; 
     144        SPExportJob             *job; 
     145        id                              exportItem; 
     146        NSUInteger              counter; 
     147         
    134148        if([item isKindOfClass:[SPPlaylistFolder class]]) { 
    135149                if(![item isLoading]) { 
     
    146160                } 
    147161        } 
     162        else if([item isKindOfClass:[SPPlaylistExportGroup class]]) { 
     163                for(exportItem in [item items]) { 
     164                        if([exportItem isKindOfClass:[SPPlaylistExportItem class]]) { 
     165                                if(![exportItem job]) { 
     166                                        name = [[[exportItem path] lastPathComponent] stringByDeletingPathExtension]; 
     167 
     168                                        if([item destination] == SPPlaylistExportToiTunes) { 
     169                                                destinationPath = [NSFileManager temporaryPathWithPrefix:name]; 
     170                                                 
     171                                                [[NSFileManager defaultManager] createDirectoryAtPath:destinationPath]; 
     172                                                 
     173                                                path = [destinationPath stringByAppendingPathComponent:[name stringByAppendingPathExtension:@"mp4"]]; 
     174                                        } else { 
     175                                                if([item destination] == SPPlaylistExportToOriginalPath) 
     176                                                        destinationPath = [[exportItem path] stringByDeletingLastPathComponent]; 
     177                                                else 
     178                                                        destinationPath = [item destinationPath]; 
     179                                                 
     180                                                path = [destinationPath stringByAppendingPathComponent:[name stringByAppendingPathExtension:@"mp4"]]; 
     181                                                counter = 2; 
     182                                                 
     183                                                while([[NSFileManager defaultManager] fileExistsAtPath:path]) { 
     184                                                        path = [destinationPath stringByAppendingPathComponent: 
     185                                                                [[NSSWF:@"%@ %u", name, counter++] stringByAppendingPathExtension:@"mp4"]]; 
     186                                                } 
     187                                        } 
     188                                         
     189                                        job = [SPExportJob exportJobWithPath:[exportItem path] 
     190                                                                                                        file:path 
     191                                                                                                  format:[(SPPlaylistExportGroup *) item format]]; 
     192                                        [job setDelegate:self]; 
     193                                        [job setPlaylistItem:exportItem]; 
     194                                         
     195                                        [exportItem setDestinationPath:path]; 
     196                                        [exportItem setJob:job]; 
     197                                 
     198                                        if(_exports >= [[NSProcessInfo processInfo] processorCount]) { 
     199                                                [_exportQueue addObject:job]; 
     200                                        } else { 
     201                                                _exports++; 
     202                                                [job start]; 
     203                                        } 
     204                                } 
     205                        } 
     206                } 
     207                 
     208                if(![item isLoading]) { 
     209                        _spinners++; 
     210                        [_progressIndicator startAnimation:self]; 
     211                        [_loader loadMetadataOfItemsInContainer:item synchronously:NO]; 
     212                } 
     213        } 
    148214        else if([item isKindOfClass:[SPPlaylistGroup class]]) { 
    149215                if(![item isLoading]) { 
     
    162228        for(item in items) { 
    163229                if([item isExpanded]) { 
    164                         [self _reloadItem:item]; 
    165                          
    166230                        [_outlineView expandItem:item]; 
    167231                         
     
    231295#pragma mark - 
    232296 
     297- (void)_setExportGroupDirectory:(NSString *)path { 
     298        NSImage         *image; 
     299         
     300        image = [[NSWorkspace sharedWorkspace] iconForFile:path]; 
     301        [image setSize:NSMakeSize(16.0, 16.0)]; 
     302         
     303        [_exportGroupDirectoryMenuItem setTitle:[[NSFileManager defaultManager] displayNameAtPath:path]]; 
     304        [_exportGroupDirectoryMenuItem setImage:image]; 
     305        [_exportGroupDirectoryMenuItem setRepresentedObject:path]; 
     306} 
     307 
     308 
     309 
     310- (void)_startNextExportJob { 
     311        SPExportJob             *job; 
     312         
     313        if([_exportQueue count] > 0) { 
     314                job = [[[_exportQueue objectAtIndex:0] retain] autorelease]; 
     315                [_exportQueue removeObjectAtIndex:0]; 
     316                _exports++; 
     317                [job start]; 
     318        } 
     319} 
     320 
     321 
     322 
     323- (BOOL)_launchiTunesAndImportFile:(NSString *)path error:(NSError **)error { 
     324        NSDictionary            *application; 
     325        FSRef                           fsRef; 
     326        AliasHandle                     alias; 
     327        AppleEvent                      request, reply; 
     328        AEBuildError            buildError; 
     329        OSType                          iTunesSignature = 'hook'; 
     330        OSStatus                        status; 
     331        BOOL                            running = NO; 
     332        const char                      *event = 
     333                "'insh':'obj '{form: indx, want: type(cLiP), seld: long(1), from:'null'()}, '----':alis (@@)"; 
     334         
     335        for(application in [[NSWorkspace sharedWorkspace] launchedApplications]) { 
     336                if([[application objectForKey:@"NSApplicationName"] isEqualToString:@"iTunes"]) { 
     337                        running = YES; 
     338                         
     339                        break; 
     340                } 
     341        } 
     342         
     343        [[NSWorkspace sharedWorkspace] launchApplication:@"iTunes"]; 
     344 
     345        if(!running) 
     346                [NSThread sleepForTimeInterval:10.0]; 
     347         
     348        status = FSPathMakeRef((UInt8 *) [path fileSystemRepresentation], &fsRef, NULL); 
     349         
     350        if(status != noErr) { 
     351                *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:status]; 
     352                 
     353                return NO; 
     354        } 
     355         
     356        status = FSNewAlias(NULL, &fsRef, &alias); 
     357         
     358        if(status != noErr) { 
     359                *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:status]; 
     360                 
     361                return NO; 
     362        } 
     363 
     364        status = AEBuildAppleEvent('hook', 
     365                                                           'Add ', 
     366                                                           typeApplSignature, 
     367                                                           &iTunesSignature, 
     368                                                           sizeof(iTunesSignature), 
     369                                                           kAutoGenerateReturnID, 
     370                                                           kAnyTransactionID, 
     371                                                           &request, 
     372                                                           &buildError, 
     373                                                           event, 
     374                                                           alias); 
     375         
     376        if(status == noErr) { 
     377                status = AESendMessage(&request, &reply, kAEWaitReply, kAEDefaultTimeout); 
     378                 
     379                if(status == noErr) 
     380                        AEDisposeDesc(&reply); 
     381                else 
     382                        *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:status]; 
     383        } else { 
     384                *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:status]; 
     385        } 
     386         
     387        AEDisposeDesc(&request); 
     388        DisposeHandle((Handle) alias); 
     389         
     390        return (status == noErr); 
     391} 
     392 
     393 
     394 
     395#pragma mark - 
     396 
    233397- (void)_validate { 
    234         [_removeButton setEnabled:([self _selectedItem] && ![[self _selectedItem] isRepresented])]; 
     398        [_removeButton setEnabled:([self _selectedItem] != NULL)]; 
    235399} 
    236400 
     
    259423        _loader = [[SPPlaylistLoader alloc] init]; 
    260424        [_loader setDelegate:self]; 
     425         
     426        _exportQueue = [[NSMutableArray alloc] init]; 
    261427         
    262428        _simplifyFilenames = [SPSettings boolForKey:SPSimplifyFilenames]; 
     
    267433                           name:NSApplicationWillTerminateNotification]; 
    268434 
    269         [[[NSWorkspace sharedWorkspace] notificationCenter] 
     435        [[[WIEventQueue sharedQueue] notificationCenter] 
    270436                addObserver:self 
    271437                   selector:@selector(eventFileWrite:) 
     
    297463        [_eventQueueItems release]; 
    298464         
     465        [_exportQueue release]; 
     466         
    299467        [_loader release]; 
    300468 
     
    307475 
    308476- (void)windowDidLoad { 
     477        NSImage                         *icon; 
    309478        NSData                          *data; 
    310479        SPPlaylistCell          *cell; 
     
    347516        [self _restoreExpansionStateForItems:[_playlist items]]; 
    348517         
    349         [[_actionPopUpButton cell] setArrowPosition:NSPopUpNoArrow]; 
    350         [[[_actionPopUpButton menu] itemAtIndex:[_actionPopUpButton indexOfSelectedItem]] setState:NSOffState]; 
    351         [_actionPopUpButton setImagePosition:NSNoImage]; 
     518        icon = [[NSWorkspace sharedWorkspace] iconForFile:[[NSWorkspace sharedWorkspace] fullPathForApplication:@"iTunes"]]; 
     519        [icon setSize:NSMakeSize(16.0, 16.0)]; 
     520        [_exportGroupiTunesMenuItem setImage:icon]; 
    352521         
    353522        [self _setRepeatMode:[SPSettings intForKey:SPPlaylistRepeat]]; 
     
    441610 
    442611 
     612- (void)exportJobProgressed:(SPExportJob *)job { 
     613        [_outlineView display]; 
     614} 
     615 
     616 
     617 
     618- (void)exportJobCompleted:(SPExportJob *)job { 
     619        NSString                                        *path; 
     620        NSError                                         *error; 
     621        SPPlaylistExportGroup           *group; 
     622        SPPlaylistExportItem            *item; 
     623        SPPlaylistFile                          *newItem; 
     624        NSUInteger                                      index; 
     625         
     626        item    = [job playlistItem]; 
     627        group   = [item parentItem]; 
     628        path    = [item destinationPath]; 
     629        index   = [[group items] indexOfObject:item]; 
     630         
     631        [group removeItem:item]; 
     632         
     633        if([group destination] == SPPlaylistExportToiTunes) { 
     634                if(![self _launchiTunesAndImportFile:path error:&error]) 
     635                        [[error alert] beginSheetModalForWindow:[self window] modalDelegate:NULL didEndSelector:NULL contextInfo:NULL]; 
     636                 
     637                [[NSFileManager defaultManager] removeItemAtPath:[path stringByDeletingLastPathComponent] error:NULL]; 
     638        } else { 
     639                newItem = [SPPlaylistFile fileWithPath:path]; 
     640                 
     641                if(index >= 0 && (NSUInteger) index < [group numberOfItems]) 
     642                        [group insertItem:newItem atIndex:index]; 
     643                else 
     644                        [group addItem:newItem]; 
     645        } 
     646 
     647        [_outlineView reloadData]; 
     648         
     649        [self _reloadItem:group]; 
     650         
     651        _exports--; 
     652         
     653        [self _startNextExportJob]; 
     654} 
     655 
     656 
     657 
     658- (void)exportJobStopped:(SPExportJob *)job { 
     659        _exports--; 
     660         
     661        [self _startNextExportJob]; 
     662} 
     663 
     664 
     665 
     666- (void)exportJob:(SPExportJob *)job failedWithError:(NSError *)error { 
     667        SPPlaylistExportGroup           *group; 
     668        SPPlaylistExportItem            *item; 
     669 
     670        [[error alert] beginSheetModalForWindow:[self window] modalDelegate:NULL didEndSelector:NULL contextInfo:NULL]; 
     671         
     672        item    = [job playlistItem]; 
     673        group   = [item parentItem]; 
     674         
     675        [group removeItem:item]; 
     676         
     677        [_outlineView reloadData]; 
     678 
     679        _exports--; 
     680 
     681        [self _startNextExportJob]; 
     682} 
     683 
     684 
     685 
    443686- (void)preferencesDidChange:(NSNotification *)notification { 
    444687        _simplifyFilenames = [SPSettings boolForKey:SPSimplifyFilenames]; 
     
    454697 
    455698- (void)movieControllerViewCountChanged:(NSNotification *)notification { 
    456         [_outlineView setNeedsDisplay]; 
     699        [_outlineView setNeedsDisplay:YES]; 
    457700} 
    458701 
     
    471714                return (item != NULL && [item isInFileSystem]); 
    472715        else if(selector == @selector(remove:)) 
    473                 return (item != NULL && ![item isRepresented]); 
     716                return (item != NULL); 
    474717         
    475718        return YES; 
     
    540783 
    541784 
     785- (NSUInteger)numberOfExports { 
     786        return _exports + [_exportQueue count]; 
     787} 
     788 
     789 
     790 
    542791#pragma mark - 
    543792 
     
    638887                          contextInfo:[item retain]]; 
    639888        } 
     889        else if([item isKindOfClass:[SPPlaylistExportGroup class]]) { 
     890                [_exportGroupNameTextField setStringValue:[item name]]; 
     891                [_exportGroupFormatPopUpButton selectItemWithTag:[(SPPlaylistExportGroup *) item format]]; 
     892                [_exportGroupDestinationPopUpButton selectItemWithTag:[item destination]]; 
     893                 
     894                if([item destinationPath]) 
     895                        [self _setExportGroupDirectory:[item destinationPath]]; 
     896                else 
     897                        [self _setExportGroupDirectory:[@"~/Desktop" stringByExpandingTildeInPath]]; 
     898                 
     899                [NSApp beginSheet:_exportGroupPanel 
     900                   modalForWindow:[self window] 
     901                        modalDelegate:self 
     902                   didEndSelector:@selector(editExportGroupSheetDidEnd:returnCode:contextInfo:) 
     903                          contextInfo:[item retain]]; 
     904        } 
    640905} 
    641906 
     
    651916                if([_outlineView isItemExpanded:smartGroup]) 
    652917                        [self _reloadItem:smartGroup]; 
     918 
     919                [self _savePlaylist]; 
    653920        } 
    654921         
    655922        [smartGroup release]; 
    656923        [_smartGroupPanel close]; 
     924} 
     925 
     926 
     927 
     928- (void)editExportGroupSheetDidEnd:(NSWindow *)sheet returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo { 
     929        SPPlaylistExportGroup   *exportGroup = contextInfo; 
     930         
     931        if(returnCode == NSAlertDefaultReturn) { 
     932                [exportGroup setName:[_exportGroupNameTextField stringValue]]; 
     933                [exportGroup setFormat:[_exportGroupFormatPopUpButton tagOfSelectedItem]]; 
     934                [exportGroup setDestination:[_exportGroupDestinationPopUpButton tagOfSelectedItem]]; 
     935                [exportGroup setDestinationPath:[_exportGroupDestinationPopUpButton representedObjectOfSelectedItem]]; 
     936 
     937                [self _savePlaylist]; 
     938        } 
     939         
     940        [exportGroup release]; 
     941        [_exportGroupPanel close]; 
    657942} 
    658943 
     
    683968        NSString                        *path; 
    684969        id                                      item, newItem; 
     970        BOOL                            exportGroup; 
    685971         
    686972        if(returnCode == NSOKButton) { 
     
    692978                if(!item) 
    693979                        item = _playlist; 
     980                 
     981                exportGroup = [item isKindOfClass:[SPPlaylistExportGroup class]]; 
    694982                 
    695983                items = [NSMutableArray array]; 
     
    698986                        if([[NSFileManager defaultManager] directoryExistsAtPath:path]) 
    699987                                newItem = [SPPlaylistFolder folderWithPath:path]; 
     988                        else if(exportGroup) 
     989                                newItem = [SPPlaylistExportItem exportItemWithPath:path]; 
    700990                        else 
    701991                                newItem = [SPPlaylistFile fileWithPath:path]; 
     
    8031093 
    8041094 
     1095- (IBAction)newExportGroup:(id)sender { 
     1096        [_exportGroupNameTextField setStringValue:NSLS(@"Export for iPhone", @"Playlist export group name")]; 
     1097 
     1098        [self _setExportGroupDirectory:[@"~/Desktop" stringByExpandingTildeInPath]]; 
     1099         
     1100        [_exportGroupDestinationPopUpButton selectItem:_exportGroupSameAsOriginalMenuItem]; 
     1101 
     1102        [NSApp beginSheet:_exportGroupPanel 
     1103           modalForWindow:[self window] 
     1104                modalDelegate:self 
     1105           didEndSelector:@selector(newExportGroupSheetDidEnd:returnCode:contextInfo:) 
     1106                  contextInfo:NULL]; 
     1107} 
     1108 
     1109 
     1110 
     1111- (void)newExportGroupSheetDidEnd:(NSWindow *)window returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo { 
     1112        SPPlaylistExportGroup   *exportGroup; 
     1113        id                                              item; 
     1114        NSInteger                               row; 
     1115         
     1116        if(returnCode == NSAlertDefaultReturn) { 
     1117                exportGroup = [SPPlaylistExportGroup exportGroupWithName:[_exportGroupNameTextField stringValue]]; 
     1118                [exportGroup setFormat:[_exportGroupFormatPopUpButton tagOfSelectedItem]]; 
     1119                [exportGroup setDestination:[_exportGroupDestinationPopUpButton tagOfSelectedItem]]; 
     1120                [exportGroup setDestinationPath:[_exportGroupDestinationPopUpButton representedObjectOfSelectedItem]]; 
     1121                 
     1122                item = [self _selectedItem]; 
     1123                 
     1124                while(item && ![item isKindOfClass:[SPPlaylistGroup class]]) 
     1125                        item = [item parentItem]; 
     1126                 
     1127                if(!item) 
     1128                        item = _playlist; 
     1129                 
     1130                [(SPPlaylistGroup *) item addItem:exportGroup]; 
     1131                 
     1132                [_outlineView expandItem:item]; 
     1133                [_outlineView reloadData]; 
     1134                 
     1135                row = [_outlineView rowForItem:exportGroup]; 
     1136                 
     1137                if(row >= 0) 
     1138                        [_outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO]; 
     1139 
     1140                [self _savePlaylist]; 
     1141        } 
     1142         
     1143        [_exportGroupPanel close]; 
     1144} 
     1145 
     1146 
     1147 
     1148- (IBAction)format:(id)sender { 
     1149        NSMutableString                 *string; 
     1150        NSMenuItem                              *item; 
     1151         
     1152        string = [[_exportGroupNameTextField stringValue] mutableCopy]; 
     1153         
     1154        for(item in [_exportGroupFormatPopUpButton itemArray]) 
     1155                [string replaceOccurrencesOfString:[item title] withString:[_exportGroupFormatPopUpButton titleOfSelectedItem]]; 
     1156         
     1157        [_exportGroupNameTextField setStringValue:string]; 
     1158        [string release]; 
     1159} 
     1160 
     1161 
     1162 
     1163- (IBAction)destination:(id)sender { 
     1164        NSOpenPanel             *openPanel; 
     1165         
     1166        openPanel = [NSOpenPanel openPanel]; 
     1167        [openPanel setCanChooseFiles:NO]; 
     1168        [openPanel setCanChooseDirectories:YES]; 
     1169        [openPanel setPrompt:NSLS(@"Select", @"Export destination open panel button")]; 
     1170         
     1171        if([openPanel runModalForDirectory:NULL file:NULL types:NULL] == NSOKButton) { 
     1172                [self _setExportGroupDirectory:[openPanel filename]]; 
     1173                 
     1174                [_exportGroupDestinationPopUpButton selectItem:_exportGroupDirectoryMenuItem]; 
     1175        } 
     1176} 
     1177 
     1178 
     1179 
    8051180- (IBAction)remove:(id)sender { 
    806         NSString        *path; 
    807         id                      item; 
     1181        NSMutableDictionary             *paths; 
     1182        NSMutableArray                  *files, *names; 
     1183        NSAlert                                 *alert; 
     1184        NSString                                *path, *title; 
     1185        id                                              item, exportItem; 
     1186         
     1187        files = [NSMutableArray array]; 
    8081188         
    8091189        for(item in [[self _selectedItems] reversedArray]) { 
    810                 if(![item isRepresented]) { 
     1190                if([item isRepresented]) { 
     1191                        [files addObject:item]; 
     1192                } else { 
    8111193                        if([item isKindOfClass:[SPPlaylistFolder class]]) { 
    8121194                                path = [item resolvedPath]; 
     
    8161198                                [_eventQueueItems removeObjectForKey:path]; 
    8171199                        } 
     1200                        else if([item isKindOfClass:[SPPlaylistExportGroup class]]) { 
     1201                                for(exportItem in [item items]) { 
     1202                                        if([exportItem isKindOfClass:[SPPlaylistExportItem class]]) 
     1203                                                [[exportItem job] stop]; 
     1204                                } 
     1205                        } 
     1206                        else if([item isKindOfClass:[SPPlaylistExportItem class]]) { 
     1207                                [[item job] stop]; 
     1208                        } 
    8181209                         
    8191210                        [(SPPlaylistGroup *) [item parentItem] removeItem:item]; 
     1211                } 
     1212        } 
     1213         
     1214        if([files count] > 0) { 
     1215                if([files count] == 1) { 
     1216                        title = [NSSWF:NSLS(@"Are you sure you want to move \"%@\" to Trash?", @"Remove file dialog title (filename)"), 
     1217                                [[files objectAtIndex:0] name]]; 
     1218                } else { 
     1219                        title = [NSSWF:NSLS(@"Are you sure you want to move %lu items to Trash?", @"Remove file dialog title (count)"), 
     1220                                [files count]]; 
     1221                } 
     1222                 
     1223                alert = [NSAlert alertWithMessageText:title 
     1224                                                                defaultButton:NSLS(@"OK", @"Remove file dialog button") 
     1225                                                          alternateButton:NSLS(@"Cancel", @"Remove file dialog button") 
     1226                                                                  otherButton:NULL informativeTextWithFormat:@""]; 
     1227                 
     1228                if([alert runModal] == NSAlertDefaultReturn) { 
     1229                        paths = [NSMutableDictionary dictionary]; 
     1230                         
     1231                        for(item in files) { 
     1232                                path = [[item resolvedPath] stringByDeletingLastPathComponent]; 
     1233                                names = [paths objectForKey:path]; 
     1234                                 
     1235                                if(!names) { 
     1236                                        names = [NSMutableArray array]; 
     1237                                        [paths setObject:names forKey:path]; 
     1238                                } 
     1239                                 
     1240                                [names addObject:[item name]]; 
     1241                        } 
     1242                         
     1243                        for(path in paths) { 
     1244                                names = [paths objectForKey:path]; 
     1245                                 
     1246                                [[NSWorkspace sharedWorkspace] 
     1247                                        performFileOperation:NSWorkspaceRecycleOperation  
     1248