1 /*****************************************************************************
2 * playlist.m: MacOS X interface module
3 *****************************************************************************
4 * Copyright (C) 2002-2006 the VideoLAN team
7 * Authors: Jon Lech Johansen <jon-vl@nanocrew.net>
8 * Derk-Jan Hartman <hartman at videola/n dot org>
9 * Benjamin Pracht <bigben at videolab dot org>
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
21 * You should have received a copy of the GNU General Public License
22 * along with this program; if not, write to the Free Software
23 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
24 *****************************************************************************/
27 * add 'icons' for different types of nodes? (http://www.cocoadev.com/index.pl?IconAndTextInTableCell)
28 * create toggle buttons for the shuffle, repeat one, repeat all functions.
29 * reimplement enable/disable item
30 * create a new 'tool' button (see the gear button in the Finder window) for 'actions'
31 (adding service discovery, other views, new node/playlist, save node/playlist) stuff like that
35 /*****************************************************************************
37 *****************************************************************************/
38 #include <stdlib.h> /* malloc(), free() */
39 #include <sys/param.h> /* for MAXPATHLEN */
42 #include <sys/mount.h>
48 #import "playlistinfo.h"
53 #import <vlc_interface.h>
55 /*****************************************************************************
56 * VLCPlaylistView implementation
57 *****************************************************************************/
58 @implementation VLCPlaylistView
60 - (NSMenu *)menuForEvent:(NSEvent *)o_event
62 return( [[self delegate] menuForEvent: o_event] );
65 - (void)keyDown:(NSEvent *)o_event
69 if( [[o_event characters] length] )
71 key = [[o_event characters] characterAtIndex: 0];
76 case NSDeleteCharacter:
77 case NSDeleteFunctionKey:
78 case NSDeleteCharFunctionKey:
79 case NSBackspaceCharacter:
80 [[self delegate] deleteItem:self];
83 case NSEnterCharacter:
84 case NSCarriageReturnCharacter:
85 [(VLCPlaylist *)[[VLCMain sharedInstance] getPlaylist]
90 [super keyDown: o_event];
98 /*****************************************************************************
99 * VLCPlaylistCommon implementation
101 * This class the superclass of the VLCPlaylist and VLCPlaylistWizard.
102 * It contains the common methods and elements of these 2 entities.
103 *****************************************************************************/
104 @implementation VLCPlaylistCommon
111 o_outline_dict = [[NSMutableDictionary alloc] init];
117 playlist_t * p_playlist = pl_Yield( VLCIntf );
118 [o_outline_view setTarget: self];
119 [o_outline_view setDelegate: self];
120 [o_outline_view setDataSource: self];
122 vlc_object_release( p_playlist );
128 [[o_tc_name headerCell] setStringValue:_NS("Name")];
129 [[o_tc_author headerCell] setStringValue:_NS("Author")];
130 [[o_tc_duration headerCell] setStringValue:_NS("Duration")];
133 - (NSOutlineView *)outlineView
135 return o_outline_view;
138 - (playlist_item_t *)selectedPlaylistItem
140 return [[o_outline_view itemAtRow: [o_outline_view selectedRow]]
146 @implementation VLCPlaylistCommon (NSOutlineViewDataSource)
148 /* return the number of children for Obj-C pointer item */ /* DONE */
149 - (int)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
152 playlist_item_t *p_item = NULL;
153 playlist_t * p_playlist = pl_Yield( VLCIntf );
154 if( outlineView != o_outline_view )
156 vlc_object_release( p_playlist );
163 p_item = p_playlist->p_root_category;
167 p_item = (playlist_item_t *)[item pointerValue];
170 i_return = p_item->i_children;
171 vlc_object_release( p_playlist );
179 /* return the child at index for the Obj-C pointer item */ /* DONE */
180 - (id)outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item
182 playlist_item_t *p_return = NULL, *p_item = NULL;
184 playlist_t * p_playlist = pl_Yield( VLCIntf );
189 p_item = p_playlist->p_root_category;
193 p_item = (playlist_item_t *)[item pointerValue];
195 if( p_item && index < p_item->i_children && index >= 0 )
196 p_return = p_item->pp_children[index];
198 vlc_object_release( p_playlist );
200 o_value = [o_outline_dict objectForKey:[NSString stringWithFormat: @"%p", p_return]];
204 o_value = [[NSValue valueWithPointer: p_return] retain];
205 msg_Err( VLCIntf, "missing playlist item's pointer value" );
210 /* is the item expandable */
211 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
214 playlist_t *p_playlist = pl_Yield( VLCIntf );
219 if( p_playlist->p_root_category )
221 i_return = p_playlist->p_root_category->i_children;
226 playlist_item_t *p_item = (playlist_item_t *)[item pointerValue];
228 i_return = p_item->i_children;
230 vlc_object_release( p_playlist );
232 return (i_return > 0);
235 /* retrieve the string values for the cells */
236 - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)o_tc byItem:(id)item
239 playlist_item_t *p_item;
241 /* For error handling */
242 static BOOL attempted_reload = NO;
244 if( item == nil || ![item isKindOfClass: [NSValue class]] )
246 /* Attempt to fix the error by asking for a data redisplay
247 * This might cause infinite loop, so add a small check */
248 if( !attempted_reload )
250 attempted_reload = YES;
251 [outlineView reloadData];
256 p_item = (playlist_item_t *)[item pointerValue];
257 if( !p_item || !p_item->p_input )
259 /* Attempt to fix the error by asking for a data redisplay
260 * This might cause infinite loop, so add a small check */
261 if( !attempted_reload )
263 attempted_reload = YES;
264 [outlineView reloadData];
269 attempted_reload = NO;
271 if( [[o_tc identifier] isEqualToString:@"1"] )
273 /* sanity check to prevent the NSString class from crashing */
274 if( !EMPTY_STR( input_item_GetTitle( p_item->p_input ) ) )
276 o_value = [NSString stringWithUTF8String: input_item_GetTitle( p_item->p_input )];
277 if( o_value == NULL )
278 o_value = [NSString stringWithCString: input_item_GetTitle( p_item->p_input )];
280 else if( p_item->p_input->psz_name != NULL )
282 o_value = [NSString stringWithUTF8String: p_item->p_input->psz_name];
283 if( o_value == NULL )
284 o_value = [NSString stringWithCString: p_item->p_input->psz_name];
287 else if( [[o_tc identifier] isEqualToString:@"2"] && !EMPTY_STR( input_item_GetArtist( p_item->p_input ) ) )
289 o_value = [NSString stringWithUTF8String: input_item_GetArtist( p_item->p_input )];
290 if( o_value == NULL )
291 o_value = [NSString stringWithCString: input_item_GetArtist( p_item->p_input )];
293 else if( [[o_tc identifier] isEqualToString:@"3"] )
295 char psz_duration[MSTRTIME_MAX_SIZE];
296 mtime_t dur = p_item->p_input->i_duration;
299 secstotimestr( psz_duration, dur/1000000 );
300 o_value = [NSString stringWithUTF8String: psz_duration];
304 o_value = @"-:--:--";
313 /*****************************************************************************
314 * VLCPlaylistWizard implementation
315 *****************************************************************************/
316 @implementation VLCPlaylistWizard
318 - (IBAction)reloadOutlineView
320 /* Only reload the outlineview if the wizard window is open since this can
321 be quite long on big playlists */
322 if( [[o_outline_view window] isVisible] )
324 [o_outline_view reloadData];
330 /*****************************************************************************
331 * extension to NSOutlineView's interface to fix compilation warnings
332 * and let us access these 2 functions properly
333 * this uses a private Apple-API, but works fine on all current OSX releases
334 * keep checking for compatiblity with future releases though
335 *****************************************************************************/
337 @interface NSOutlineView (UndocumentedSortImages)
338 + (NSImage *)_defaultTableHeaderSortImage;
339 + (NSImage *)_defaultTableHeaderReverseSortImage;
343 /*****************************************************************************
344 * VLCPlaylist implementation
345 *****************************************************************************/
346 @implementation VLCPlaylist
353 o_nodes_array = [[NSMutableArray alloc] init];
354 o_items_array = [[NSMutableArray alloc] init];
361 playlist_t * p_playlist = pl_Yield( VLCIntf );
362 vlc_list_t *p_list = vlc_list_find( p_playlist, VLC_OBJECT_MODULE,
367 [super awakeFromNib];
369 [o_outline_view setDoubleAction: @selector(playItem:)];
371 [o_outline_view registerForDraggedTypes:
372 [NSArray arrayWithObjects: NSFilenamesPboardType,
373 @"VLCPlaylistItemPboardType", nil]];
374 [o_outline_view setIntercellSpacing: NSMakeSize (0.0, 1.0)];
376 /* this uses private Apple API which works fine until 10.4,
377 * but keep checking in the future!
378 * These methods are being added artificially to NSOutlineView's interface above */
379 o_ascendingSortingImage = [[NSOutlineView class] _defaultTableHeaderSortImage];
380 o_descendingSortingImage = [[NSOutlineView class] _defaultTableHeaderReverseSortImage];
382 o_tc_sortColumn = nil;
384 for( i_index = 0; i_index < p_list->i_count; i_index++ )
386 vlc_bool_t b_enabled;
389 module_t *p_parser = (module_t *)p_list->p_values[i_index].p_object ;
391 if( !strcmp( p_parser->psz_capability, "services_discovery" ) )
393 /* Check for submodules */
395 while( p_parser->pp_shortcuts[++i] != NULL ); i--;
397 /* Check whether to enable these menuitems */
398 objectname = i>=0 ? (char *)p_parser->pp_shortcuts[i] : (char *)p_parser->psz_object_name;
399 b_enabled = playlist_IsServicesDiscoveryLoaded( p_playlist, objectname );
401 /* Create the menu entries used in the playlist menu */
402 o_lmi = [[o_mi_services submenu] addItemWithTitle:
403 [NSString stringWithUTF8String:
404 p_parser->psz_longname ? p_parser->psz_longname :
405 ( p_parser->psz_shortname ? p_parser->psz_shortname:
407 action: @selector(servicesChange:)
409 [o_lmi setTarget: self];
410 [o_lmi setRepresentedObject: [NSString stringWithCString: objectname]];
411 if( b_enabled ) [o_lmi setState: NSOnState];
413 /* Create the menu entries for the main menu */
414 o_lmi = [[o_mm_mi_services submenu] addItemWithTitle:
415 [NSString stringWithUTF8String:
416 p_parser->psz_longname ? p_parser->psz_longname :
417 ( p_parser->psz_shortname ? p_parser->psz_shortname:
419 action: @selector(servicesChange:)
421 [o_lmi setTarget: self];
422 [o_lmi setRepresentedObject: [NSString stringWithCString:objectname]];
423 if( b_enabled ) [o_lmi setState: NSOnState];
426 vlc_list_release( p_list );
427 vlc_object_release( p_playlist );
429 //[self playlistUpdated];
432 - (void)searchfieldChanged:(NSNotification *)o_notification
434 [o_search_field setStringValue:[[o_notification object] stringValue]];
441 [o_mi_save_playlist setTitle: _NS("Save Playlist...")];
442 [o_mi_play setTitle: _NS("Play")];
443 [o_mi_delete setTitle: _NS("Delete")];
444 [o_mi_recursive_expand setTitle: _NS("Expand Node")];
445 [o_mi_selectall setTitle: _NS("Select All")];
446 [o_mi_info setTitle: _NS("Information")];
447 [o_mi_preparse setTitle: _NS("Get Stream Information")];
448 [o_mi_sort_name setTitle: _NS("Sort Node by Name")];
449 [o_mi_sort_author setTitle: _NS("Sort Node by Author")];
450 [o_mi_services setTitle: _NS("Services discovery")];
451 [o_status_field setStringValue: [NSString stringWithFormat:
452 _NS("No items in the playlist")]];
455 [o_search_button setTitle: _NS("Search")];
457 [o_search_field setToolTip: _NS("Search in Playlist")];
458 [o_mi_addNode setTitle: _NS("Add Folder to Playlist")];
460 [o_save_accessory_text setStringValue: _NS("File Format:")];
461 [[o_save_accessory_popup itemAtIndex:0] setTitle: _NS("Extended M3U")];
462 [[o_save_accessory_popup itemAtIndex:1] setTitle: _NS("XML Shareable Playlist Format (XSPF)")];
465 - (void)playlistUpdated
469 /* Clear indications of any existing column sorting */
470 for( i = 0 ; i < [[o_outline_view tableColumns] count] ; i++ )
472 [o_outline_view setIndicatorImage:nil inTableColumn:
473 [[o_outline_view tableColumns] objectAtIndex:i]];
476 [o_outline_view setHighlightedTableColumn:nil];
477 o_tc_sortColumn = nil;
478 // TODO Find a way to keep the dict size to a minimum
479 //[o_outline_dict removeAllObjects];
480 [o_outline_view reloadData];
481 [[[[VLCMain sharedInstance] getWizard] getPlaylistWizard] reloadOutlineView];
482 [[[[VLCMain sharedInstance] getBookmarks] getDataTable] reloadData];
484 playlist_t *p_playlist = pl_Yield( VLCIntf );
486 if( playlist_CurrentSize( p_playlist ) >= 2 )
488 [o_status_field setStringValue: [NSString stringWithFormat:
489 _NS("%i items in the playlist"),
490 playlist_CurrentSize( p_playlist )]];
494 if( playlist_IsEmpty( p_playlist ) )
495 [o_status_field setStringValue: _NS("No items in the playlist")];
497 [o_status_field setStringValue: _NS("1 item in the playlist")];
499 vlc_object_release( p_playlist );
502 - (void)playModeUpdated
504 playlist_t *p_playlist = pl_Yield( VLCIntf );
505 vlc_value_t val, val2;
507 var_Get( p_playlist, "loop", &val2 );
508 var_Get( p_playlist, "repeat", &val );
509 if( val.b_bool == VLC_TRUE )
511 [[[VLCMain sharedInstance] getControls] repeatOne];
513 else if( val2.b_bool == VLC_TRUE )
515 [[[VLCMain sharedInstance] getControls] repeatAll];
519 [[[VLCMain sharedInstance] getControls] repeatOff];
522 [[[VLCMain sharedInstance] getControls] shuffle];
524 vlc_object_release( p_playlist );
527 - (void)updateRowSelection
532 playlist_t *p_playlist = pl_Yield( VLCIntf );
533 playlist_item_t *p_item, *p_temp_item;
534 NSMutableArray *o_array = [NSMutableArray array];
536 p_item = p_playlist->status.p_item;
539 vlc_object_release(p_playlist);
543 p_temp_item = p_item;
544 while( p_temp_item->p_parent )
546 [o_array insertObject: [NSValue valueWithPointer: p_temp_item] atIndex: 0];
547 p_temp_item = p_temp_item->p_parent;
548 /*for (i = 0 ; i < p_temp_item->i_parents ; i++)
550 if( p_temp_item->pp_parents[i]->i_view == i_current_view )
552 p_temp_item = p_temp_item->pp_parents[i]->p_parent;
558 for( j = 0; j < [o_array count] - 1; j++ )
561 if( ( o_item = [o_outline_dict objectForKey:
562 [NSString stringWithFormat: @"%p",
563 [[o_array objectAtIndex:j] pointerValue]]] ) != nil )
565 [o_outline_view expandItem: o_item];
570 i_row = [o_outline_view rowForItem:[o_outline_dict
571 objectForKey:[NSString stringWithFormat: @"%p", p_item]]];
573 [o_outline_view selectRow: i_row byExtendingSelection: NO];
574 [o_outline_view scrollRowToVisible: i_row];
576 vlc_object_release( p_playlist );
578 /* update our info-panel to reflect the new item */
579 [[[VLCMain sharedInstance] getInfo] updatePanel];
582 /* Check if p_item is a child of p_node recursively. We need to check the item
583 existence first since OSX sometimes tries to redraw items that have been
584 deleted. We don't do it when not required since this verification takes
585 quite a long time on big playlists (yes, pretty hacky). */
586 - (BOOL)isItem: (playlist_item_t *)p_item
587 inNode: (playlist_item_t *)p_node
588 checkItemExistence:(BOOL)b_check
591 playlist_t * p_playlist = pl_Yield( VLCIntf );
592 playlist_item_t *p_temp_item = p_item;
594 if( p_node == p_item )
596 vlc_object_release(p_playlist);
600 if( p_node->i_children < 1)
602 vlc_object_release(p_playlist);
609 vlc_mutex_lock( &p_playlist->object_lock );
613 /* Since outlineView: willDisplayCell:... may call this function with
614 p_items that don't exist anymore, first check if the item is still
615 in the playlist. Any cleaner solution welcomed. */
616 for( i = 0; i < p_playlist->all_items.i_size; i++ )
618 if( ARRAY_VAL( p_playlist->all_items, i) == p_item ) break;
619 else if ( i == p_playlist->all_items.i_size - 1 )
621 vlc_object_release( p_playlist );
622 vlc_mutex_unlock( &p_playlist->object_lock );
630 p_temp_item = p_temp_item->p_parent;
631 if( p_temp_item == p_node )
633 vlc_mutex_unlock( &p_playlist->object_lock );
634 vlc_object_release( p_playlist );
638 vlc_mutex_unlock( &p_playlist->object_lock );
641 vlc_object_release( p_playlist );
645 /* This method is usefull for instance to remove the selected children of an
646 already selected node */
647 - (void)removeItemsFrom:(id)o_items ifChildrenOf:(id)o_nodes
650 for( i = 0 ; i < [o_items count] ; i++ )
652 for ( j = 0 ; j < [o_nodes count] ; j++ )
654 if( o_items == o_nodes)
656 if( j == i ) continue;
658 if( [self isItem: [[o_items objectAtIndex:i] pointerValue]
659 inNode: [[o_nodes objectAtIndex:j] pointerValue]
660 checkItemExistence: NO] )
662 [o_items removeObjectAtIndex:i];
663 /* We need to execute the next iteration with the same index
664 since the current item has been deleted */
673 - (IBAction)savePlaylist:(id)sender
675 intf_thread_t * p_intf = VLCIntf;
676 playlist_t * p_playlist = pl_Yield( p_intf );
678 NSSavePanel *o_save_panel = [NSSavePanel savePanel];
679 NSString * o_name = [NSString stringWithFormat: @"%@", _NS("Untitled")];
681 //[o_save_panel setAllowedFileTypes: [NSArray arrayWithObjects: @"m3u", @"xpf", nil] ];
682 [o_save_panel setTitle: _NS("Save Playlist")];
683 [o_save_panel setPrompt: _NS("Save")];
684 [o_save_panel setAccessoryView: o_save_accessory_view];
686 if( [o_save_panel runModalForDirectory: nil
687 file: o_name] == NSOKButton )
689 NSString *o_filename = [o_save_panel filename];
691 if( [o_save_accessory_popup indexOfSelectedItem] == 1 )
693 NSString * o_real_filename;
695 range.location = [o_filename length] - [@".xspf" length];
696 range.length = [@".xspf" length];
698 if( [o_filename compare:@".xspf" options: NSCaseInsensitiveSearch
699 range: range] != NSOrderedSame )
701 o_real_filename = [NSString stringWithFormat: @"%@.xspf", o_filename];
705 o_real_filename = o_filename;
707 playlist_Export( p_playlist,
708 [o_real_filename fileSystemRepresentation],
709 p_playlist->p_local_category, "export-xspf" );
713 NSString * o_real_filename;
715 range.location = [o_filename length] - [@".m3u" length];
716 range.length = [@".m3u" length];
718 if( [o_filename compare:@".m3u" options: NSCaseInsensitiveSearch
719 range: range] != NSOrderedSame )
721 o_real_filename = [NSString stringWithFormat: @"%@.m3u", o_filename];
725 o_real_filename = o_filename;
727 playlist_Export( p_playlist,
728 [o_real_filename fileSystemRepresentation],
729 p_playlist->p_local_category, "export-m3u" );
732 vlc_object_release( p_playlist );
735 /* When called retrieves the selected outlineview row and plays that node or item */
736 - (IBAction)playItem:(id)sender
738 intf_thread_t * p_intf = VLCIntf;
739 playlist_t * p_playlist = pl_Yield( p_intf );
741 playlist_item_t *p_item;
742 playlist_item_t *p_node = NULL;
744 p_item = [[o_outline_view itemAtRow:[o_outline_view selectedRow]] pointerValue];
748 if( p_item->i_children == -1 )
750 p_node = p_item->p_parent;
756 if( p_node->i_children > 0 && p_node->pp_children[0]->i_children == -1 )
758 p_item = p_node->pp_children[0];
765 playlist_Control( p_playlist, PLAYLIST_VIEWPLAY, VLC_TRUE, p_node, p_item );
767 vlc_object_release( p_playlist );
770 /* When called retrieves the selected outlineview row and plays that node or item */
771 - (IBAction)preparseItem:(id)sender
774 NSMutableArray *o_to_preparse;
775 intf_thread_t * p_intf = VLCIntf;
776 playlist_t * p_playlist = pl_Yield( p_intf );
778 o_to_preparse = [NSMutableArray arrayWithArray:[[o_outline_view selectedRowEnumerator] allObjects]];
779 i_count = [o_to_preparse count];
783 playlist_item_t *p_item = NULL;
785 for( i = 0; i < i_count; i++ )
787 o_number = [o_to_preparse lastObject];
788 i_row = [o_number intValue];
789 p_item = [[o_outline_view itemAtRow:i_row] pointerValue];
790 [o_to_preparse removeObject: o_number];
791 [o_outline_view deselectRow: i_row];
795 if( p_item->i_children == -1 )
797 playlist_PreparseEnqueue( p_playlist, p_item->p_input );
801 msg_Dbg( p_intf, "preparse of nodes not yet implemented" );
805 vlc_object_release( p_playlist );
806 [self playlistUpdated];
809 - (IBAction)servicesChange:(id)sender
811 NSMenuItem *o_mi = (NSMenuItem *)sender;
812 NSString *o_string = [o_mi representedObject];
813 playlist_t * p_playlist = pl_Yield( VLCIntf );
814 if( !playlist_IsServicesDiscoveryLoaded( p_playlist, [o_string UTF8String] ) )
815 playlist_ServicesDiscoveryAdd( p_playlist, [o_string UTF8String] );
817 playlist_ServicesDiscoveryRemove( p_playlist, [o_string UTF8String] );
819 [o_mi setState: playlist_IsServicesDiscoveryLoaded( p_playlist,
820 [o_string UTF8String] ) ? YES : NO];
822 vlc_object_release( p_playlist );
823 [self playlistUpdated];
827 - (IBAction)selectAll:(id)sender
829 [o_outline_view selectAll: nil];
832 - (IBAction)deleteItem:(id)sender
834 int i, i_count, i_row;
835 NSMutableArray *o_to_delete;
838 playlist_t * p_playlist;
839 intf_thread_t * p_intf = VLCIntf;
841 p_playlist = pl_Yield( p_intf );
843 o_to_delete = [NSMutableArray arrayWithArray:[[o_outline_view selectedRowEnumerator] allObjects]];
844 i_count = [o_to_delete count];
846 for( i = 0; i < i_count; i++ )
848 o_number = [o_to_delete lastObject];
849 i_row = [o_number intValue];
850 id o_item = [o_outline_view itemAtRow: i_row];
851 playlist_item_t *p_item = [o_item pointerValue];
852 [o_to_delete removeObject: o_number];
853 [o_outline_view deselectRow: i_row];
855 if( [[o_outline_view dataSource] outlineView:o_outline_view
856 numberOfChildrenOfItem: o_item] > 0 )
857 //is a node and not an item
859 if( p_playlist->status.i_status != PLAYLIST_STOPPED &&
860 [self isItem: p_playlist->status.p_item inNode:
861 ((playlist_item_t *)[o_item pointerValue])
862 checkItemExistence: NO] == YES )
864 // if current item is in selected node and is playing then stop playlist
865 playlist_Stop( p_playlist );
867 vlc_mutex_lock( &p_playlist->object_lock );
868 playlist_NodeDelete( p_playlist, p_item, VLC_TRUE, VLC_FALSE );
869 vlc_mutex_unlock( &p_playlist->object_lock );
873 playlist_DeleteFromInput( p_playlist, p_item->p_input->i_id, VLC_FALSE );
876 [self playlistUpdated];
877 vlc_object_release( p_playlist );
880 - (IBAction)sortNodeByName:(id)sender
882 [self sortNode: SORT_TITLE];
885 - (IBAction)sortNodeByAuthor:(id)sender
887 [self sortNode: SORT_ARTIST];
890 - (void)sortNode:(int)i_mode
892 playlist_t * p_playlist = pl_Yield( VLCIntf );
893 playlist_item_t * p_item;
895 if( [o_outline_view selectedRow] > -1 )
897 p_item = [[o_outline_view itemAtRow: [o_outline_view selectedRow]]
901 /*If no item is selected, sort the whole playlist*/
903 p_item = p_playlist->p_root_category;
906 if( p_item->i_children > -1 ) // the item is a node
908 vlc_mutex_lock( &p_playlist->object_lock );
909 playlist_RecursiveNodeSort( p_playlist, p_item, i_mode, ORDER_NORMAL );
910 vlc_mutex_unlock( &p_playlist->object_lock );
914 vlc_mutex_lock( &p_playlist->object_lock );
915 playlist_RecursiveNodeSort( p_playlist,
916 p_item->p_parent, i_mode, ORDER_NORMAL );
917 vlc_mutex_unlock( &p_playlist->object_lock );
919 vlc_object_release( p_playlist );
920 [self playlistUpdated];
923 - (input_item_t *)createItem:(NSDictionary *)o_one_item
925 intf_thread_t * p_intf = VLCIntf;
926 playlist_t * p_playlist = pl_Yield( p_intf );
928 input_item_t *p_input;
930 BOOL b_rem = FALSE, b_dir = FALSE;
931 NSString *o_uri, *o_name;
936 o_uri = (NSString *)[o_one_item objectForKey: @"ITEM_URL"];
937 o_name = (NSString *)[o_one_item objectForKey: @"ITEM_NAME"];
938 o_options = (NSArray *)[o_one_item objectForKey: @"ITEM_OPTIONS"];
940 /* Find the name for a disc entry ( i know, can you believe the trouble?) */
941 if( ( !o_name || [o_name isEqualToString:@""] ) && [o_uri rangeOfString: @"/dev/"].location != NSNotFound )
943 int i_count, i_index;
944 struct statfs *mounts = NULL;
946 i_count = getmntinfo (&mounts, MNT_NOWAIT);
947 /* getmntinfo returns a pointer to static data. Do not free. */
948 for( i_index = 0 ; i_index < i_count; i_index++ )
950 NSMutableString *o_temp, *o_temp2;
951 o_temp = [NSMutableString stringWithString: o_uri];
952 o_temp2 = [NSMutableString stringWithCString: mounts[i_index].f_mntfromname];
953 [o_temp replaceOccurrencesOfString: @"/dev/rdisk" withString: @"/dev/disk" options:nil range:NSMakeRange(0, [o_temp length]) ];
954 [o_temp2 replaceOccurrencesOfString: @"s0" withString: @"" options:nil range:NSMakeRange(0, [o_temp2 length]) ];
955 [o_temp2 replaceOccurrencesOfString: @"s1" withString: @"" options:nil range:NSMakeRange(0, [o_temp2 length]) ];
957 if( strstr( [o_temp fileSystemRepresentation], [o_temp2 fileSystemRepresentation] ) != NULL )
959 o_name = [[NSFileManager defaultManager] displayNameAtPath: [NSString stringWithCString:mounts[i_index].f_mntonname]];
963 /* If no name, then make a guess */
964 if( !o_name) o_name = [[NSFileManager defaultManager] displayNameAtPath: o_uri];
966 if( [[NSFileManager defaultManager] fileExistsAtPath:o_uri isDirectory:&b_dir] && b_dir &&
967 [[NSWorkspace sharedWorkspace] getFileSystemInfoForPath: o_uri isRemovable: &b_rem
968 isWritable:NULL isUnmountable:NULL description:NULL type:NULL] && b_rem )
970 /* All of this is to make sure CD's play when you D&D them on VLC */
971 /* Converts mountpoint to a /dev file */
974 NSMutableString *o_temp;
976 buf = (struct statfs *) malloc (sizeof(struct statfs));
977 statfs( [o_uri fileSystemRepresentation], buf );
978 psz_dev = strdup(buf->f_mntfromname);
979 o_temp = [NSMutableString stringWithCString: psz_dev ];
980 [o_temp replaceOccurrencesOfString: @"/dev/disk" withString: @"/dev/rdisk" options:nil range:NSMakeRange(0, [o_temp length]) ];
981 [o_temp replaceOccurrencesOfString: @"s0" withString: @"" options:nil range:NSMakeRange(0, [o_temp length]) ];
982 [o_temp replaceOccurrencesOfString: @"s1" withString: @"" options:nil range:NSMakeRange(0, [o_temp length]) ];
986 p_input = input_ItemNew( p_playlist, [o_uri fileSystemRepresentation], [o_name UTF8String] );
992 for( i = 0; i < (int)[o_options count]; i++ )
994 input_ItemAddOption( p_input, strdup( [[o_options objectAtIndex:i] UTF8String] ) );
998 /* Recent documents menu */
999 o_true_file = [NSURL fileURLWithPath: o_uri];
1000 if( o_true_file != nil )
1002 [[NSDocumentController sharedDocumentController]
1003 noteNewRecentDocumentURL: o_true_file];
1006 vlc_object_release( p_playlist );
1010 - (void)appendArray:(NSArray*)o_array atPos:(int)i_position enqueue:(BOOL)b_enqueue
1013 playlist_t * p_playlist = pl_Yield( VLCIntf );
1015 for( i_item = 0; i_item < (int)[o_array count]; i_item++ )
1017 input_item_t *p_input;
1018 NSDictionary *o_one_item;
1021 o_one_item = [o_array objectAtIndex: i_item];
1022 p_input = [self createItem: o_one_item];
1029 playlist_AddInput( p_playlist, p_input, PLAYLIST_INSERT,
1030 i_position == -1 ? PLAYLIST_END : i_position + i_item, VLC_TRUE,
1033 if( i_item == 0 && !b_enqueue )
1035 playlist_item_t *p_item;
1036 p_item = playlist_ItemGetByInput( p_playlist, p_input, VLC_TRUE );
1037 playlist_Control( p_playlist, PLAYLIST_VIEWPLAY, VLC_TRUE, NULL, p_item );
1041 playlist_item_t *p_item;
1042 p_item = playlist_ItemGetByInput( p_playlist, p_input, VLC_TRUE );
1043 playlist_Control( p_playlist, PLAYLIST_PREPARSE, VLC_TRUE, p_item );
1046 [self playlistUpdated];
1047 vlc_object_release( p_playlist );
1050 - (void)appendNodeArray:(NSArray*)o_array inNode:(playlist_item_t *)p_node atPos:(int)i_position enqueue:(BOOL)b_enqueue
1053 playlist_t * p_playlist = pl_Yield( VLCIntf );
1055 for( i_item = 0; i_item < (int)[o_array count]; i_item++ )
1057 input_item_t *p_input;
1058 NSDictionary *o_one_item;
1061 o_one_item = [o_array objectAtIndex: i_item];
1062 p_input = [self createItem: o_one_item];
1069 playlist_NodeAddInput( p_playlist, p_input, p_node,
1072 PLAYLIST_END : i_position + i_item, VLC_FALSE );
1075 if( i_item == 0 && !b_enqueue )
1077 playlist_item_t *p_item;
1078 p_item = playlist_ItemGetByInput( p_playlist, p_input, VLC_TRUE );
1079 playlist_Control( p_playlist, PLAYLIST_VIEWPLAY, VLC_TRUE, NULL, p_item );
1083 playlist_item_t *p_item;
1084 p_item = playlist_ItemGetByInput( p_playlist, p_input, VLC_TRUE );
1085 playlist_Control( p_playlist, PLAYLIST_PREPARSE, VLC_TRUE, p_item );
1088 [self playlistUpdated];
1089 vlc_object_release( p_playlist );
1092 - (NSMutableArray *)subSearchItem:(playlist_item_t *)p_item
1094 playlist_t *p_playlist = pl_Yield( VLCIntf );
1095 playlist_item_t *p_selected_item;
1096 int i_current, i_selected_row;
1098 i_selected_row = [o_outline_view selectedRow];
1099 if (i_selected_row < 0)
1102 p_selected_item = (playlist_item_t *)[[o_outline_view itemAtRow:
1103 i_selected_row] pointerValue];
1105 for( i_current = 0; i_current < p_item->i_children ; i_current++ )
1108 NSString *o_current_name, *o_current_author;
1110 vlc_mutex_lock( &p_playlist->object_lock );
1111 o_current_name = [NSString stringWithUTF8String:
1112 p_item->pp_children[i_current]->p_input->psz_name];
1113 psz_temp = input_ItemGetInfo( p_item->p_input ,
1114 _("Meta-information"),_("Artist") );
1115 o_current_author = [NSString stringWithUTF8String: psz_temp];
1117 vlc_mutex_unlock( &p_playlist->object_lock );
1119 if( p_selected_item == p_item->pp_children[i_current] &&
1120 b_selected_item_met == NO )
1122 b_selected_item_met = YES;
1124 else if( p_selected_item == p_item->pp_children[i_current] &&
1125 b_selected_item_met == YES )
1127 vlc_object_release( p_playlist );
1130 else if( b_selected_item_met == YES &&
1131 ( [o_current_name rangeOfString:[o_search_field
1132 stringValue] options:NSCaseInsensitiveSearch ].length ||
1133 [o_current_author rangeOfString:[o_search_field
1134 stringValue] options:NSCaseInsensitiveSearch ].length ) )
1136 vlc_object_release( p_playlist );
1137 /*Adds the parent items in the result array as well, so that we can
1139 return [NSMutableArray arrayWithObject: [NSValue
1140 valueWithPointer: p_item->pp_children[i_current]]];
1142 if( p_item->pp_children[i_current]->i_children > 0 )
1144 id o_result = [self subSearchItem:
1145 p_item->pp_children[i_current]];
1146 if( o_result != NULL )
1148 vlc_object_release( p_playlist );
1149 [o_result insertObject: [NSValue valueWithPointer:
1150 p_item->pp_children[i_current]] atIndex:0];
1155 vlc_object_release( p_playlist );
1159 - (IBAction)searchItem:(id)sender
1161 playlist_t * p_playlist = pl_Yield( VLCIntf );
1167 b_selected_item_met = NO;
1169 /*First, only search after the selected item:*
1170 *(b_selected_item_met = NO) */
1171 o_result = [self subSearchItem:p_playlist->p_root_category];
1172 if( o_result == NULL )
1174 /* If the first search failed, search again from the beginning */
1175 o_result = [self subSearchItem:p_playlist->p_root_category];
1177 if( o_result != NULL )
1180 if( [[o_result objectAtIndex: 0] pointerValue] ==
1181 p_playlist->p_local_category )
1186 for( i = i_start ; i < [o_result count] - 1 ; i++ )
1188 [o_outline_view expandItem: [o_outline_dict objectForKey:
1189 [NSString stringWithFormat: @"%p",
1190 [[o_result objectAtIndex: i] pointerValue]]]];
1192 i_row = [o_outline_view rowForItem: [o_outline_dict objectForKey:
1193 [NSString stringWithFormat: @"%p",
1194 [[o_result objectAtIndex: [o_result count] - 1 ]
1199 [o_outline_view selectRow:i_row byExtendingSelection: NO];
1200 [o_outline_view scrollRowToVisible: i_row];
1202 vlc_object_release( p_playlist );
1205 - (IBAction)recursiveExpandNode:(id)sender
1207 id o_item = [o_outline_view itemAtRow: [o_outline_view selectedRow]];
1208 playlist_item_t *p_item = (playlist_item_t *)[o_item pointerValue];
1210 if( ![[o_outline_view dataSource] outlineView: o_outline_view
1211 isItemExpandable: o_item] )
1213 o_item = [o_outline_dict objectForKey: [NSString
1214 stringWithFormat: @"%p", p_item->p_parent]];
1217 /* We need to collapse the node first, since OSX refuses to recursively
1218 expand an already expanded node, even if children nodes are collapsed. */
1219 [o_outline_view collapseItem: o_item collapseChildren: YES];
1220 [o_outline_view expandItem: o_item expandChildren: YES];
1223 - (NSMenu *)menuForEvent:(NSEvent *)o_event
1227 vlc_bool_t b_item_sel;
1229 pt = [o_outline_view convertPoint: [o_event locationInWindow]
1231 b_item_sel = ( [o_outline_view rowAtPoint: pt] != -1 &&
1232 [o_outline_view selectedRow] != -1 );
1233 b_rows = [o_outline_view numberOfRows] != 0;
1235 [o_mi_play setEnabled: b_item_sel];
1236 [o_mi_delete setEnabled: b_item_sel];
1237 [o_mi_selectall setEnabled: b_rows];
1238 [o_mi_info setEnabled: b_item_sel];
1239 [o_mi_preparse setEnabled: b_item_sel];
1240 [o_mi_recursive_expand setEnabled: b_item_sel];
1241 [o_mi_sort_name setEnabled: b_item_sel];
1242 [o_mi_sort_author setEnabled: b_item_sel];
1244 return( o_ctx_menu );
1247 - (void)outlineView: (NSTableView*)o_tv
1248 didClickTableColumn:(NSTableColumn *)o_tc
1250 int i_mode = 0, i_type;
1251 intf_thread_t *p_intf = VLCIntf;
1253 playlist_t *p_playlist = pl_Yield( p_intf );
1255 /* Check whether the selected table column header corresponds to a
1256 sortable table column*/
1257 if( !( o_tc == o_tc_name || o_tc == o_tc_author ) )
1259 vlc_object_release( p_playlist );
1263 if( o_tc_sortColumn == o_tc )
1265 b_isSortDescending = !b_isSortDescending;
1269 b_isSortDescending = VLC_FALSE;
1272 if( o_tc == o_tc_name )
1274 i_mode = SORT_TITLE;
1276 else if( o_tc == o_tc_author )
1278 i_mode = SORT_ARTIST;
1281 if( b_isSortDescending )
1283 i_type = ORDER_REVERSE;
1287 i_type = ORDER_NORMAL;
1290 vlc_mutex_lock( &p_playlist->object_lock );
1291 playlist_RecursiveNodeSort( p_playlist, p_playlist->p_root_category, i_mode, i_type );
1292 vlc_mutex_unlock( &p_playlist->object_lock );
1294 vlc_object_release( p_playlist );
1295 [self playlistUpdated];
1297 o_tc_sortColumn = o_tc;
1298 [o_outline_view setHighlightedTableColumn:o_tc];
1300 if( b_isSortDescending )
1302 [o_outline_view setIndicatorImage:o_descendingSortingImage
1303 inTableColumn:o_tc];
1307 [o_outline_view setIndicatorImage:o_ascendingSortingImage
1308 inTableColumn:o_tc];
1313 - (void)outlineView:(NSOutlineView *)outlineView
1314 willDisplayCell:(id)cell
1315 forTableColumn:(NSTableColumn *)tableColumn
1318 playlist_t *p_playlist = pl_Yield( VLCIntf );
1322 o_playing_item = [o_outline_dict objectForKey:
1323 [NSString stringWithFormat:@"%p", p_playlist->status.p_item]];
1325 if( [self isItem: [o_playing_item pointerValue] inNode:
1326 [item pointerValue] checkItemExistence: YES]
1327 || [o_playing_item isEqual: item] )
1329 [cell setFont: [NSFont boldSystemFontOfSize: 0]];
1333 [cell setFont: [NSFont systemFontOfSize: 0]];
1335 vlc_object_release( p_playlist );
1338 - (IBAction)addNode:(id)sender
1340 /* we have to create a new thread here because otherwise we would block the
1341 * interface since the interaction-stuff and this code would run in the same
1343 [NSThread detachNewThreadSelector: @selector(addNodeThreadedly)
1344 toTarget: self withObject:nil];
1345 [self playlistUpdated];
1348 - (void)addNodeThreadedly
1350 NSAutoreleasePool * ourPool = [[NSAutoreleasePool alloc] init];
1352 /* simply adds a new node to the end of the playlist */
1353 playlist_t * p_playlist = pl_Yield( VLCIntf );
1354 vlc_thread_set_priority( p_playlist, VLC_THREAD_PRIORITY_LOW );
1357 char *psz_name = NULL;
1358 playlist_item_t * p_item;
1359 ret_v = intf_UserStringInput( p_playlist, _("New Node"),
1360 _("Please enter a name for the new node."), &psz_name );
1362 if( psz_name != NULL && psz_name != "" )
1363 p_item = playlist_NodeCreate( p_playlist, psz_name,
1364 p_playlist->p_local_category, 0 );
1365 else if(! config_GetInt( p_playlist, "interact" ) )
1367 /* in case that the interaction is disabled, just give it a bogus name */
1368 p_item = playlist_NodeCreate( p_playlist, _("Empty Folder"),
1369 p_playlist->p_local_category, 0 );
1373 msg_Warn( VLCIntf, "node creation failed or cancelled by user" );
1375 vlc_object_release( p_playlist );
1381 @implementation VLCPlaylist (NSOutlineViewDataSource)
1383 - (id)outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item
1385 id o_value = [super outlineView: outlineView child: index ofItem: item];
1386 playlist_t *p_playlist = pl_Yield( VLCIntf );
1388 if( playlist_CurrentSize( p_playlist ) >= 2 )
1390 [o_status_field setStringValue: [NSString stringWithFormat:
1391 _NS("%i items in the playlist"),
1392 playlist_CurrentSize( p_playlist )]];
1396 if( playlist_IsEmpty( p_playlist ) )
1398 [o_status_field setStringValue: _NS("No items in the playlist")];
1402 [o_status_field setStringValue: _NS("1 item in the playlist")];
1405 vlc_object_release( p_playlist );
1407 [o_outline_dict setObject:o_value forKey:[NSString stringWithFormat:@"%p",
1408 [o_value pointerValue]]];
1409 msg_Dbg( VLCIntf, "adding item %p", [o_value pointerValue] );
1414 /* Required for drag & drop and reordering */
1415 - (BOOL)outlineView:(NSOutlineView *)outlineView writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pboard
1418 playlist_t *p_playlist = pl_Yield( VLCIntf );
1420 /* First remove the items that were moved during the last drag & drop
1422 [o_items_array removeAllObjects];
1423 [o_nodes_array removeAllObjects];
1425 for( i = 0 ; i < [items count] ; i++ )
1427 id o_item = [items objectAtIndex: i];
1429 /* Refuse to move items that are not in the General Node
1430 (Service Discovery) */
1431 if( ![self isItem: [o_item pointerValue] inNode:
1432 p_playlist->p_local_category checkItemExistence: NO])
1434 vlc_object_release(p_playlist);
1437 /* Fill the items and nodes to move in 2 different arrays */
1438 if( ((playlist_item_t *)[o_item pointerValue])->i_children > 0 )
1439 [o_nodes_array addObject: o_item];
1441 [o_items_array addObject: o_item];
1444 /* Now we need to check if there are selected items that are in already
1445 selected nodes. In that case, we only want to move the nodes */
1446 [self removeItemsFrom: o_nodes_array ifChildrenOf: o_nodes_array];
1447 [self removeItemsFrom: o_items_array ifChildrenOf: o_nodes_array];
1449 /* We add the "VLCPlaylistItemPboardType" type to be able to recognize
1450 a Drop operation coming from the playlist. */
1452 [pboard declareTypes: [NSArray arrayWithObjects:
1453 @"VLCPlaylistItemPboardType", nil] owner: self];
1454 [pboard setData:[NSData data] forType:@"VLCPlaylistItemPboardType"];
1456 vlc_object_release(p_playlist);
1460 - (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id <NSDraggingInfo>)info proposedItem:(id)item proposedChildIndex:(int)index
1462 playlist_t *p_playlist = pl_Yield( VLCIntf );
1463 NSPasteboard *o_pasteboard = [info draggingPasteboard];
1465 if( !p_playlist ) return NSDragOperationNone;
1467 /* Dropping ON items is not allowed if item is not a node */
1470 if( index == NSOutlineViewDropOnItemIndex &&
1471 ((playlist_item_t *)[item pointerValue])->i_children == -1 )
1473 vlc_object_release( p_playlist );
1474 return NSDragOperationNone;
1478 /* Don't allow on drop on playlist root element's child */
1479 if( !item && index != NSOutlineViewDropOnItemIndex)
1481 vlc_object_release( p_playlist );
1482 return NSDragOperationNone;
1485 /* We refuse to drop an item in anything else than a child of the General
1486 Node. We still accept items that would be root nodes of the outlineview
1487 however, to allow drop in an empty playlist. */
1488 if( !([self isItem: [item pointerValue] inNode: p_playlist->p_local_category
1489 checkItemExistence: NO] || item == nil) )
1491 vlc_object_release( p_playlist );
1492 return NSDragOperationNone;
1495 /* Drop from the Playlist */
1496 if( [[o_pasteboard types] containsObject: @"VLCPlaylistItemPboardType"] )
1499 for( i = 0 ; i < [o_nodes_array count] ; i++ )
1501 /* We refuse to Drop in a child of an item we are moving */
1502 if( [self isItem: [item pointerValue] inNode:
1503 [[o_nodes_array objectAtIndex: i] pointerValue]
1504 checkItemExistence: NO] )
1506 vlc_object_release( p_playlist );
1507 return NSDragOperationNone;
1510 vlc_object_release( p_playlist );
1511 return NSDragOperationMove;
1514 /* Drop from the Finder */
1515 else if( [[o_pasteboard types] containsObject: NSFilenamesPboardType] )
1517 vlc_object_release( p_playlist );
1518 return NSDragOperationGeneric;
1520 vlc_object_release( p_playlist );
1521 return NSDragOperationNone;
1524 - (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id <NSDraggingInfo>)info item:(id)item childIndex:(int)index
1526 playlist_t * p_playlist = pl_Yield( VLCIntf );
1527 NSPasteboard *o_pasteboard = [info draggingPasteboard];
1529 /* Drag & Drop inside the playlist */
1530 if( [[o_pasteboard types] containsObject: @"VLCPlaylistItemPboardType"] )
1532 int i_row, i_removed_from_node = 0;
1534 playlist_item_t *p_new_parent, *p_item = NULL;
1535 NSArray *o_all_items = [o_nodes_array arrayByAddingObjectsFromArray:
1537 /* If the item is to be dropped as root item of the outline, make it a
1538 child of the General node.
1539 Else, choose the proposed parent as parent. */
1540 if( item == nil ) p_new_parent = p_playlist->p_local_category;
1541 else p_new_parent = [item pointerValue];
1543 /* Make sure the proposed parent is a node.
1544 (This should never be true) */
1545 if( p_new_parent->i_children < 0 )
1547 vlc_object_release( p_playlist );
1551 for( i = 0; i < [o_all_items count]; i++ )
1553 playlist_item_t *p_old_parent = NULL;
1554 int i_old_index = 0;
1556 p_item = [[o_all_items objectAtIndex:i] pointerValue];
1557 p_old_parent = p_item->p_parent;
1560 /* We may need the old index later */
1561 if( p_new_parent == p_old_parent )
1564 for( j = 0; j < p_old_parent->i_children; j++ )
1566 if( p_old_parent->pp_children[j] == p_item )
1574 vlc_mutex_lock( &p_playlist->object_lock );
1575 // Acually detach the item from the old position
1576 if( playlist_NodeRemoveItem( p_playlist, p_item, p_old_parent ) ==
1580 /* Calculate the new index */
1583 /* If we move the item in the same node, we need to take into
1584 account that one item will be deleted */
1587 if ((p_new_parent == p_old_parent &&
1588 i_old_index < index + (int)i) )
1590 i_removed_from_node++;
1592 i_new_index = index + i - i_removed_from_node;
1594 // Reattach the item to the new position
1595 playlist_NodeInsert( p_playlist, p_item, p_new_parent, i_new_index );
1597 vlc_mutex_unlock( &p_playlist->object_lock );
1599 [self playlistUpdated];
1600 i_row = [o_outline_view rowForItem:[o_outline_dict
1601 objectForKey:[NSString stringWithFormat: @"%p",
1602 [[o_all_items objectAtIndex: 0] pointerValue]]]];
1606 i_row = [o_outline_view rowForItem:[o_outline_dict
1607 objectForKey:[NSString stringWithFormat: @"%p", p_new_parent]]];
1610 [o_outline_view deselectAll: self];
1611 [o_outline_view selectRow: i_row byExtendingSelection: NO];
1612 [o_outline_view scrollRowToVisible: i_row];
1614 vlc_object_release( p_playlist );
1618 else if( [[o_pasteboard types] containsObject: NSFilenamesPboardType] )
1621 playlist_item_t *p_node = [item pointerValue];
1623 NSArray *o_array = [NSArray array];
1624 NSArray *o_values = [[o_pasteboard propertyListForType:
1625 NSFilenamesPboardType]
1626 sortedArrayUsingSelector:
1627 @selector(caseInsensitiveCompare:)];
1629 for( i = 0; i < (int)[o_values count]; i++)
1631 NSDictionary *o_dic;
1632 o_dic = [NSDictionary dictionaryWithObject:[o_values
1633 objectAtIndex:i] forKey:@"ITEM_URL"];
1634 o_array = [o_array arrayByAddingObject: o_dic];
1639 [self appendArray: o_array atPos: index enqueue: YES];
1641 /* This should never occur */
1642 else if( p_node->i_children == -1 )
1644 vlc_object_release( p_playlist );
1649 [self appendNodeArray: o_array inNode: p_node
1650 atPos: index enqueue: YES];
1652 vlc_object_release( p_playlist );
1655 vlc_object_release( p_playlist );
1659 /* Delegate method of NSWindow */
1660 /*- (void)windowWillClose:(NSNotification *)aNotification
1662 [o_btn_playlist setState: NSOffState];