1 /*****************************************************************************
2 * playlist.m: MacOS X interface module
3 *****************************************************************************
4 * Copyright (C) 2002-2005 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 a new search field build with pictures from the 'regular' search field, so it can be emulated on 10.2
29 * create toggle buttons for the shuffle, repeat one, repeat all functions.
30 * implement drag and drop and item reordering.
31 * reimplement enable/disable item
32 * create a new 'tool' button (see the gear button in the Finder window) for 'actions'
33 (adding service discovery, other views, new node/playlist, save node/playlist) stuff like that
37 /*****************************************************************************
39 *****************************************************************************/
40 #include <stdlib.h> /* malloc(), free() */
41 #include <sys/param.h> /* for MAXPATHLEN */
44 #include <sys/mount.h>
50 #import "playlistinfo.h"
56 /*****************************************************************************
57 * VLCPlaylistView implementation
58 *****************************************************************************/
59 @implementation VLCPlaylistView
61 - (NSMenu *)menuForEvent:(NSEvent *)o_event
63 return( [[self delegate] menuForEvent: o_event] );
66 - (void)keyDown:(NSEvent *)o_event
70 if( [[o_event characters] length] )
72 key = [[o_event characters] characterAtIndex: 0];
77 case NSDeleteCharacter:
78 case NSDeleteFunctionKey:
79 case NSDeleteCharFunctionKey:
80 case NSBackspaceCharacter:
81 [[self delegate] deleteItem:self];
84 case NSEnterCharacter:
85 case NSCarriageReturnCharacter:
86 [(VLCPlaylist *)[[VLCMain sharedInstance] getPlaylist]
91 [super keyDown: o_event];
99 /*****************************************************************************
100 * VLCPlaylistCommon implementation
102 * This class the superclass of the VLCPlaylist and VLCPlaylistWizard.
103 * It contains the common methods and elements of these 2 entities.
104 *****************************************************************************/
105 @implementation VLCPlaylistCommon
112 o_outline_dict = [[NSMutableDictionary alloc] init];
118 playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
120 i_current_view = VIEW_CATEGORY;
121 playlist_ViewUpdate( p_playlist, i_current_view );
123 [o_outline_view setTarget: self];
124 [o_outline_view setDelegate: self];
125 [o_outline_view setDataSource: self];
127 vlc_object_release( p_playlist );
133 [[o_tc_name headerCell] setStringValue:_NS("Name")];
134 [[o_tc_author headerCell] setStringValue:_NS("Author")];
135 [[o_tc_duration headerCell] setStringValue:_NS("Duration")];
138 - (NSOutlineView *)outlineView
140 return o_outline_view;
143 - (playlist_item_t *)selectedPlaylistItem
145 return [[o_outline_view itemAtRow: [o_outline_view selectedRow]]
151 @implementation VLCPlaylistCommon (NSOutlineViewDataSource)
153 /* return the number of children for Obj-C pointer item */ /* DONE */
154 - (int)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
157 playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
159 if( p_playlist == NULL )
161 if( outlineView != o_outline_view )
163 vlc_object_release( p_playlist );
170 playlist_view_t *p_view;
171 p_view = playlist_ViewFind( p_playlist, i_current_view );
172 if( p_view && p_view->p_root )
174 i_return = p_view->p_root->i_children;
176 if( i_current_view == VIEW_CATEGORY )
178 i_return--; /* remove the GENERAL item from the list */
179 i_return += p_playlist->p_general->i_children; /* add the items of the general node */
185 playlist_item_t *p_item = (playlist_item_t *)[item pointerValue];
187 i_return = p_item->i_children;
189 vlc_object_release( p_playlist );
197 /* return the child at index for the Obj-C pointer item */ /* DONE */
198 - (id)outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item
200 playlist_item_t *p_return = NULL;
201 playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
205 if( p_playlist == NULL )
211 playlist_view_t *p_view;
212 p_view = playlist_ViewFind( p_playlist, i_current_view );
213 if( p_view && p_view->p_root ) p_return = p_view->p_root->pp_children[index];
215 if( i_current_view == VIEW_CATEGORY )
217 if( p_playlist->p_general->i_children && index >= 0 && index < p_playlist->p_general->i_children )
219 p_return = p_playlist->p_general->pp_children[index];
221 else if( p_view && p_view->p_root && index >= 0 && index - p_playlist->p_general->i_children < p_view->p_root->i_children )
223 p_return = p_view->p_root->pp_children[index - p_playlist->p_general->i_children + 1];
229 playlist_item_t *p_item = (playlist_item_t *)[item pointerValue];
230 if( p_item && index < p_item->i_children && index >= 0 )
231 p_return = p_item->pp_children[index];
235 vlc_object_release( p_playlist );
237 o_value = [o_outline_dict objectForKey:[NSString stringWithFormat: @"%p", p_return]];
240 o_value = [[NSValue valueWithPointer: p_return] retain];
245 /* is the item expandable */
246 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
249 playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
251 if( p_playlist == NULL )
257 playlist_view_t *p_view;
258 p_view = playlist_ViewFind( p_playlist, i_current_view );
259 if( p_view && p_view->p_root ) i_return = p_view->p_root->i_children;
261 if( i_current_view == VIEW_CATEGORY )
264 i_return += p_playlist->p_general->i_children;
269 playlist_item_t *p_item = (playlist_item_t *)[item pointerValue];
271 i_return = p_item->i_children;
273 vlc_object_release( p_playlist );
281 /* retrieve the string values for the cells */
282 - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)o_tc byItem:(id)item
285 intf_thread_t *p_intf = VLCIntf;
286 playlist_t *p_playlist;
287 playlist_item_t *p_item;
289 if( item == nil || ![item isKindOfClass: [NSValue class]] ) return( @"error" );
291 p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
293 if( p_playlist == NULL )
298 p_item = (playlist_item_t *)[item pointerValue];
302 vlc_object_release( p_playlist );
306 if( [[o_tc identifier] isEqualToString:@"1"] )
308 o_value = [NSString stringWithUTF8String:
309 p_item->input.psz_name];
310 if( o_value == NULL )
311 o_value = [NSString stringWithCString:
312 p_item->input.psz_name];
314 else if( [[o_tc identifier] isEqualToString:@"2"] )
317 psz_temp = vlc_input_item_GetInfo( &p_item->input ,_("Meta-information"),_("Artist") );
319 if( psz_temp == NULL )
323 o_value = [NSString stringWithUTF8String: psz_temp];
324 if( o_value == NULL )
326 o_value = [NSString stringWithCString: psz_temp];
331 else if( [[o_tc identifier] isEqualToString:@"3"] )
333 char psz_duration[MSTRTIME_MAX_SIZE];
334 mtime_t dur = p_item->input.i_duration;
337 secstotimestr( psz_duration, dur/1000000 );
338 o_value = [NSString stringWithUTF8String: psz_duration];
342 o_value = @"-:--:--";
345 vlc_object_release( p_playlist );
352 /*****************************************************************************
353 * VLCPlaylistWizard implementation
354 *****************************************************************************/
355 @implementation VLCPlaylistWizard
357 - (IBAction)reloadOutlineView
359 /* Only reload the outlineview if the wizard window is open since this can
360 be quite long on big playlists */
361 if( [[o_outline_view window] isVisible] )
363 [o_outline_view reloadData];
369 /*****************************************************************************
370 * VLCPlaylist implementation
371 *****************************************************************************/
372 @implementation VLCPlaylist
379 o_nodes_array = [[NSMutableArray alloc] init];
380 o_items_array = [[NSMutableArray alloc] init];
387 playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
389 vlc_list_t *p_list = vlc_list_find( p_playlist, VLC_OBJECT_MODULE,
394 [super awakeFromNib];
396 [o_outline_view setDoubleAction: @selector(playItem:)];
398 [o_outline_view registerForDraggedTypes:
399 [NSArray arrayWithObjects: NSFilenamesPboardType,
400 @"VLCPlaylistItemPboardType", nil]];
401 [o_outline_view setIntercellSpacing: NSMakeSize (0.0, 1.0)];
403 /* We need to check whether _defaultTableHeaderSortImage exists, since it
404 belongs to an Apple hidden private API, and then can "disapear" at any time*/
406 if( [[NSOutlineView class] respondsToSelector:@selector(_defaultTableHeaderSortImage)] )
408 o_ascendingSortingImage = [[NSOutlineView class] _defaultTableHeaderSortImage];
412 o_ascendingSortingImage = nil;
415 if( [[NSOutlineView class] respondsToSelector:@selector(_defaultTableHeaderReverseSortImage)] )
417 o_descendingSortingImage = [[NSOutlineView class] _defaultTableHeaderReverseSortImage];
421 o_descendingSortingImage = nil;
424 o_tc_sortColumn = nil;
426 for( i_index = 0; i_index < p_list->i_count; i_index++ )
429 module_t * p_parser = (module_t *)p_list->p_values[i_index].p_object ;
431 if( !strcmp( p_parser->psz_capability, "services_discovery" ) )
433 /* create the menu entries used in the playlist menu */
434 o_lmi = [[o_mi_services submenu] addItemWithTitle:
435 [NSString stringWithUTF8String:
436 p_parser->psz_longname ? p_parser->psz_longname :
437 ( p_parser->psz_shortname ? p_parser->psz_shortname:
438 p_parser->psz_object_name)]
439 action: @selector(servicesChange:)
441 [o_lmi setTarget: self];
442 [o_lmi setRepresentedObject:
443 [NSString stringWithCString: p_parser->psz_object_name]];
444 if( playlist_IsServicesDiscoveryLoaded( p_playlist,
445 p_parser->psz_object_name ) )
446 [o_lmi setState: NSOnState];
448 /* create the menu entries for the main menu */
449 o_lmi = [[o_mm_mi_services submenu] addItemWithTitle:
450 [NSString stringWithUTF8String:
451 p_parser->psz_longname ? p_parser->psz_longname :
452 ( p_parser->psz_shortname ? p_parser->psz_shortname:
453 p_parser->psz_object_name)]
454 action: @selector(servicesChange:)
456 [o_lmi setTarget: self];
457 [o_lmi setRepresentedObject:
458 [NSString stringWithCString: p_parser->psz_object_name]];
459 if( playlist_IsServicesDiscoveryLoaded( p_playlist,
460 p_parser->psz_object_name ) )
461 [o_lmi setState: NSOnState];
464 vlc_list_release( p_list );
465 vlc_object_release( p_playlist );
467 //[self playlistUpdated];
470 - (void)searchfieldChanged:(NSNotification *)o_notification
472 [o_search_field setStringValue:[[o_notification object] stringValue]];
479 [o_mi_save_playlist setTitle: _NS("Save Playlist...")];
480 [o_mi_play setTitle: _NS("Play")];
481 [o_mi_delete setTitle: _NS("Delete")];
482 [o_mi_recursive_expand setTitle: _NS("Expand Node")];
483 [o_mi_selectall setTitle: _NS("Select All")];
484 [o_mi_info setTitle: _NS("Information")];
485 [o_mi_preparse setTitle: _NS("Get Stream Information")];
486 [o_mi_sort_name setTitle: _NS("Sort Node by Name")];
487 [o_mi_sort_author setTitle: _NS("Sort Node by Author")];
488 [o_mi_services setTitle: _NS("Services discovery")];
489 [o_status_field setStringValue: [NSString stringWithFormat:
490 _NS("No items in the playlist")]];
492 [o_random_ckb setTitle: _NS("Random")];
494 [o_search_button setTitle: _NS("Search")];
496 [o_search_field setToolTip: _NS("Search in Playlist")];
497 [[o_loop_popup itemAtIndex:0] setTitle: _NS("Standard Play")];
498 [[o_loop_popup itemAtIndex:1] setTitle: _NS("Repeat One")];
499 [[o_loop_popup itemAtIndex:2] setTitle: _NS("Repeat All")];
500 [o_mi_addNode setTitle: _NS("Add Folder to Playlist")];
502 [o_save_accessory_text setStringValue: _NS("File Format:")];
503 [[o_save_accessory_popup itemAtIndex:0] setTitle: _NS("Extended M3U")];
504 [[o_save_accessory_popup itemAtIndex:1] setTitle: _NS("XML Shareable Playlist Format (XSPF)")];
507 - (void)playlistUpdated
511 /* Clear indications of any existing column sorting */
512 for( i = 0 ; i < [[o_outline_view tableColumns] count] ; i++ )
514 [o_outline_view setIndicatorImage:nil inTableColumn:
515 [[o_outline_view tableColumns] objectAtIndex:i]];
518 [o_outline_view setHighlightedTableColumn:nil];
519 o_tc_sortColumn = nil;
520 // TODO Find a way to keep the dict size to a minimum
521 //[o_outline_dict removeAllObjects];
522 [o_outline_view reloadData];
523 [[[[VLCMain sharedInstance] getWizard] getPlaylistWizard] reloadOutlineView];
524 [[[[VLCMain sharedInstance] getBookmarks] getDataTable] reloadData];
527 - (void)playModeUpdated
529 playlist_t *p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
531 vlc_value_t val, val2;
533 if( p_playlist == NULL )
538 var_Get( p_playlist, "loop", &val2 );
539 var_Get( p_playlist, "repeat", &val );
540 if( val.b_bool == VLC_TRUE )
542 [o_loop_popup selectItemAtIndex: 1];
544 else if( val2.b_bool == VLC_TRUE )
546 [o_loop_popup selectItemAtIndex: 2];
550 [o_loop_popup selectItemAtIndex: 0];
553 var_Get( p_playlist, "random", &val );
554 [o_random_ckb setState: val.b_bool];
556 vlc_object_release( p_playlist );
559 - (playlist_item_t *)parentOfItem:(playlist_item_t *)p_item
562 for( i = 0 ; i < p_item->i_parents; i++ )
564 if( p_item->pp_parents[i]->i_view == i_current_view )
566 return p_item->pp_parents[i]->p_parent;
572 - (void)updateRowSelection
577 playlist_t *p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
579 playlist_item_t *p_item, *p_temp_item;
580 NSMutableArray *o_array = [NSMutableArray array];
582 if( p_playlist == NULL )
585 p_item = p_playlist->status.p_item;
588 vlc_object_release(p_playlist);
592 p_temp_item = p_item;
593 while( p_temp_item->i_parents > 0 )
595 [o_array insertObject: [NSValue valueWithPointer: p_temp_item] atIndex: 0];
597 p_temp_item = [self parentOfItem: p_temp_item];
598 /*for (i = 0 ; i < p_temp_item->i_parents ; i++)
600 if( p_temp_item->pp_parents[i]->i_view == i_current_view )
602 p_temp_item = p_temp_item->pp_parents[i]->p_parent;
608 for (j = 0 ; j < [o_array count] - 1 ; j++)
611 if( ( o_item = [o_outline_dict objectForKey:
612 [NSString stringWithFormat: @"%p",
613 [[o_array objectAtIndex:j] pointerValue]]] ) != nil )
614 [o_outline_view expandItem: o_item];
618 i_row = [o_outline_view rowForItem:[o_outline_dict
619 objectForKey:[NSString stringWithFormat: @"%p", p_item]]];
621 [o_outline_view selectRow: i_row byExtendingSelection: NO];
622 [o_outline_view scrollRowToVisible: i_row];
624 vlc_object_release(p_playlist);
626 /* update our info-panel to reflect the new item */
627 [[[VLCMain sharedInstance] getInfo] updatePanel];
630 /* Check if p_item is a child of p_node recursively. We need to check the item
631 existence first since OSX sometimes tries to redraw items that have been
632 deleted. We don't do it when not required since this verification takes
633 quite a long time on big playlists (yes, pretty hacky). */
634 - (BOOL)isItem: (playlist_item_t *)p_item
635 inNode: (playlist_item_t *)p_node
636 checkItemExistence:(BOOL)b_check
639 playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
641 playlist_item_t *p_temp_item = p_item;
643 if( p_playlist == NULL )
648 if( p_node == p_item )
650 vlc_object_release(p_playlist);
654 if( p_node->i_children < 1)
656 vlc_object_release(p_playlist);
663 vlc_mutex_lock( &p_playlist->object_lock );
667 /* Since outlineView: willDisplayCell:... may call this function with
668 p_items that don't exist anymore, first check if the item is still
669 in the playlist. Any cleaner solution welcomed. */
670 for( i = 0; i < p_playlist->i_all_size; i++ )
672 if( p_playlist->pp_all_items[i] == p_item ) break;
673 else if ( i == p_playlist->i_all_size - 1 )
675 vlc_object_release( p_playlist );
676 vlc_mutex_unlock( &p_playlist->object_lock );
682 while( p_temp_item->i_parents > 0 )
684 p_temp_item = [self parentOfItem: p_temp_item];
685 if( p_temp_item == p_node )
687 vlc_mutex_unlock( &p_playlist->object_lock );
688 vlc_object_release( p_playlist );
692 /* for( i = 0; i < p_temp_item->i_parents ; i++ )
694 if( p_temp_item->pp_parents[i]->i_view == i_current_view )
696 if( p_temp_item->pp_parents[i]->p_parent == p_node )
698 vlc_mutex_unlock( &p_playlist->object_lock );
699 vlc_object_release( p_playlist );
704 p_temp_item = p_temp_item->pp_parents[i]->p_parent;
710 vlc_mutex_unlock( &p_playlist->object_lock );
713 vlc_object_release( p_playlist );
717 /* This method is usefull for instance to remove the selected children of an
718 already selected node */
719 - (void)removeItemsFrom:(id)o_items ifChildrenOf:(id)o_nodes
722 for( i = 0 ; i < [o_items count] ; i++ )
724 for ( j = 0 ; j < [o_nodes count] ; j++ )
726 if( o_items == o_nodes)
728 if( j == i ) continue;
730 if( [self isItem: [[o_items objectAtIndex:i] pointerValue]
731 inNode: [[o_nodes objectAtIndex:j] pointerValue]
732 checkItemExistence: NO] )
734 [o_items removeObjectAtIndex:i];
735 /* We need to execute the next iteration with the same index
736 since the current item has been deleted */
745 - (IBAction)savePlaylist:(id)sender
747 intf_thread_t * p_intf = VLCIntf;
748 playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
751 NSSavePanel *o_save_panel = [NSSavePanel savePanel];
752 NSString * o_name = [NSString stringWithFormat: @"%@", _NS("Untitled")];
754 //[o_save_panel setAllowedFileTypes: [NSArray arrayWithObjects: @"m3u", @"xpf", nil] ];
755 [o_save_panel setTitle: _NS("Save Playlist")];
756 [o_save_panel setPrompt: _NS("Save")];
757 [o_save_panel setAccessoryView: o_save_accessory_view];
759 if( [o_save_panel runModalForDirectory: nil
760 file: o_name] == NSOKButton )
762 NSString *o_filename = [o_save_panel filename];
764 if( [o_save_accessory_popup indexOfSelectedItem] == 1 )
766 NSString * o_real_filename;
768 range.location = [o_filename length] - [@".xspf" length];
769 range.length = [@".xspf" length];
771 if( [o_filename compare:@".xspf" options: NSCaseInsensitiveSearch
772 range: range] != NSOrderedSame )
774 o_real_filename = [NSString stringWithFormat: @"%@.xspf", o_filename];
778 o_real_filename = o_filename;
780 playlist_Export( p_playlist, [o_real_filename fileSystemRepresentation], "export-xspf" );
784 NSString * o_real_filename;
786 range.location = [o_filename length] - [@".m3u" length];
787 range.length = [@".m3u" length];
789 if( [o_filename compare:@".m3u" options: NSCaseInsensitiveSearch
790 range: range] != NSOrderedSame )
792 o_real_filename = [NSString stringWithFormat: @"%@.m3u", o_filename];
796 o_real_filename = o_filename;
798 playlist_Export( p_playlist, [o_real_filename fileSystemRepresentation], "export-m3u" );
801 vlc_object_release( p_playlist );
804 /* When called retrieves the selected outlineview row and plays that node or item */
805 - (IBAction)playItem:(id)sender
807 intf_thread_t * p_intf = VLCIntf;
808 playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
811 if( p_playlist != NULL )
813 playlist_item_t *p_item;
814 playlist_item_t *p_node = NULL;
816 p_item = [[o_outline_view itemAtRow:[o_outline_view selectedRow]] pointerValue];
820 if( p_item->i_children == -1 )
822 p_node = [self parentOfItem: p_item];
824 /* for( i = 0 ; i < p_item->i_parents ; i++ )
826 if( p_item->pp_parents[i]->i_view == i_current_view )
828 p_node = p_item->pp_parents[i]->p_parent;
835 if( p_node->i_children > 0 && p_node->pp_children[0]->i_children == -1 )
837 p_item = p_node->pp_children[0];
844 playlist_Control( p_playlist, PLAYLIST_VIEWPLAY, i_current_view, p_node, p_item );
846 vlc_object_release( p_playlist );
850 /* When called retrieves the selected outlineview row and plays that node or item */
851 - (IBAction)preparseItem:(id)sender
854 NSMutableArray *o_to_preparse;
855 intf_thread_t * p_intf = VLCIntf;
856 playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
859 o_to_preparse = [NSMutableArray arrayWithArray:[[o_outline_view selectedRowEnumerator] allObjects]];
860 i_count = [o_to_preparse count];
862 if( p_playlist != NULL )
866 playlist_item_t *p_item = NULL;
868 for( i = 0; i < i_count; i++ )
870 o_number = [o_to_preparse lastObject];
871 i_row = [o_number intValue];
872 p_item = [[o_outline_view itemAtRow:i_row] pointerValue];
873 [o_to_preparse removeObject: o_number];
874 [o_outline_view deselectRow: i_row];
878 if( p_item->i_children == -1 )
880 playlist_PreparseEnqueue( p_playlist, &p_item->input );
884 msg_Dbg( p_intf, "preparse of nodes not yet implemented" );
888 vlc_object_release( p_playlist );
890 [self playlistUpdated];
893 - (IBAction)servicesChange:(id)sender
895 NSMenuItem *o_mi = (NSMenuItem *)sender;
896 NSString *o_string = [o_mi representedObject];
897 playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
899 if( !playlist_IsServicesDiscoveryLoaded( p_playlist, [o_string cString] ) )
900 playlist_ServicesDiscoveryAdd( p_playlist, [o_string cString] );
902 playlist_ServicesDiscoveryRemove( p_playlist, [o_string cString] );
904 [o_mi setState: playlist_IsServicesDiscoveryLoaded( p_playlist,
905 [o_string cString] ) ? YES : NO];
907 i_current_view = VIEW_CATEGORY;
908 playlist_ViewUpdate( p_playlist, i_current_view );
909 vlc_object_release( p_playlist );
910 [self playlistUpdated];
914 - (IBAction)selectAll:(id)sender
916 [o_outline_view selectAll: nil];
919 - (IBAction)deleteItem:(id)sender
921 int i, i_count, i_row;
922 NSMutableArray *o_to_delete;
925 playlist_t * p_playlist;
926 intf_thread_t * p_intf = VLCIntf;
928 p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
931 if ( p_playlist == NULL )
935 o_to_delete = [NSMutableArray arrayWithArray:[[o_outline_view selectedRowEnumerator] allObjects]];
936 i_count = [o_to_delete count];
938 for( i = 0; i < i_count; i++ )
940 o_number = [o_to_delete lastObject];
941 i_row = [o_number intValue];
942 id o_item = [o_outline_view itemAtRow: i_row];
943 playlist_item_t *p_item = [o_item pointerValue];
944 [o_to_delete removeObject: o_number];
945 [o_outline_view deselectRow: i_row];
947 if( [[o_outline_view dataSource] outlineView:o_outline_view
948 numberOfChildrenOfItem: o_item] > 0 )
949 //is a node and not an item
951 if( p_playlist->status.i_status != PLAYLIST_STOPPED &&
952 [self isItem: p_playlist->status.p_item inNode:
953 ((playlist_item_t *)[o_item pointerValue])
954 checkItemExistence: NO] == YES )
956 // if current item is in selected node and is playing then stop playlist
957 playlist_Stop( p_playlist );
959 vlc_mutex_lock( &p_playlist->object_lock );
960 playlist_NodeDelete( p_playlist, p_item, VLC_TRUE, VLC_FALSE );
961 vlc_mutex_unlock( &p_playlist->object_lock );
965 if( p_playlist->status.i_status != PLAYLIST_STOPPED &&
966 p_playlist->status.p_item == [[o_outline_view itemAtRow: i_row] pointerValue] )
968 playlist_Stop( p_playlist );
970 vlc_mutex_lock( &p_playlist->object_lock );
971 playlist_Delete( p_playlist, p_item->input.i_id );
972 vlc_mutex_unlock( &p_playlist->object_lock );
975 [self playlistUpdated];
976 vlc_object_release( p_playlist );
979 - (IBAction)sortNodeByName:(id)sender
981 [self sortNode: SORT_TITLE];
984 - (IBAction)sortNodeByAuthor:(id)sender
986 [self sortNode: SORT_AUTHOR];
989 - (void)sortNode:(int)i_mode
991 playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
993 playlist_item_t * p_item;
995 if (p_playlist == NULL)
1000 if( [o_outline_view selectedRow] > -1 )
1002 p_item = [[o_outline_view itemAtRow: [o_outline_view selectedRow]]
1006 /*If no item is selected, sort the whole playlist*/
1008 playlist_view_t * p_view = playlist_ViewFind( p_playlist, i_current_view );
1009 p_item = p_view->p_root;
1012 if( p_item->i_children > -1 ) // the item is a node
1014 vlc_mutex_lock( &p_playlist->object_lock );
1015 playlist_RecursiveNodeSort( p_playlist, p_item, i_mode, ORDER_NORMAL );
1016 vlc_mutex_unlock( &p_playlist->object_lock );
1022 for( i = 0 ; i < p_item->i_parents ; i++ )
1024 if( p_item->pp_parents[i]->i_view == i_current_view )
1026 vlc_mutex_lock( &p_playlist->object_lock );
1027 playlist_RecursiveNodeSort( p_playlist,
1028 p_item->pp_parents[i]->p_parent, i_mode, ORDER_NORMAL );
1029 vlc_mutex_unlock( &p_playlist->object_lock );
1034 vlc_object_release( p_playlist );
1035 [self playlistUpdated];
1038 - (playlist_item_t *)createItem:(NSDictionary *)o_one_item
1040 intf_thread_t * p_intf = VLCIntf;
1041 playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
1044 if( p_playlist == NULL )
1048 playlist_item_t *p_item;
1050 BOOL b_rem = FALSE, b_dir = FALSE;
1051 NSString *o_uri, *o_name;
1056 o_uri = (NSString *)[o_one_item objectForKey: @"ITEM_URL"];
1057 o_name = (NSString *)[o_one_item objectForKey: @"ITEM_NAME"];
1058 o_options = (NSArray *)[o_one_item objectForKey: @"ITEM_OPTIONS"];
1060 /* Find the name for a disc entry ( i know, can you believe the trouble?) */
1061 if( ( !o_name || [o_name isEqualToString:@""] ) && [o_uri rangeOfString: @"/dev/"].location != NSNotFound )
1063 int i_count, i_index;
1064 struct statfs *mounts = NULL;
1066 i_count = getmntinfo (&mounts, MNT_NOWAIT);
1067 /* getmntinfo returns a pointer to static data. Do not free. */
1068 for( i_index = 0 ; i_index < i_count; i_index++ )
1070 NSMutableString *o_temp, *o_temp2;
1071 o_temp = [NSMutableString stringWithString: o_uri];
1072 o_temp2 = [NSMutableString stringWithCString: mounts[i_index].f_mntfromname];
1073 [o_temp replaceOccurrencesOfString: @"/dev/rdisk" withString: @"/dev/disk" options:nil range:NSMakeRange(0, [o_temp length]) ];
1074 [o_temp2 replaceOccurrencesOfString: @"s0" withString: @"" options:nil range:NSMakeRange(0, [o_temp2 length]) ];
1075 [o_temp2 replaceOccurrencesOfString: @"s1" withString: @"" options:nil range:NSMakeRange(0, [o_temp2 length]) ];
1077 if( strstr( [o_temp fileSystemRepresentation], [o_temp2 fileSystemRepresentation] ) != NULL )
1079 o_name = [[NSFileManager defaultManager] displayNameAtPath: [NSString stringWithCString:mounts[i_index].f_mntonname]];
1083 /* If no name, then make a guess */
1084 if( !o_name) o_name = [[NSFileManager defaultManager] displayNameAtPath: o_uri];
1086 if( [[NSFileManager defaultManager] fileExistsAtPath:o_uri isDirectory:&b_dir] && b_dir &&
1087 [[NSWorkspace sharedWorkspace] getFileSystemInfoForPath: o_uri isRemovable: &b_rem
1088 isWritable:NULL isUnmountable:NULL description:NULL type:NULL] && b_rem )
1090 /* All of this is to make sure CD's play when you D&D them on VLC */
1091 /* Converts mountpoint to a /dev file */
1094 NSMutableString *o_temp;
1096 buf = (struct statfs *) malloc (sizeof(struct statfs));
1097 statfs( [o_uri fileSystemRepresentation], buf );
1098 psz_dev = strdup(buf->f_mntfromname);
1099 o_temp = [NSMutableString stringWithCString: psz_dev ];
1100 [o_temp replaceOccurrencesOfString: @"/dev/disk" withString: @"/dev/rdisk" options:nil range:NSMakeRange(0, [o_temp length]) ];
1101 [o_temp replaceOccurrencesOfString: @"s0" withString: @"" options:nil range:NSMakeRange(0, [o_temp length]) ];
1102 [o_temp replaceOccurrencesOfString: @"s1" withString: @"" options:nil range:NSMakeRange(0, [o_temp length]) ];
1106 p_item = playlist_ItemNew( p_intf, [o_uri fileSystemRepresentation], [o_name UTF8String] );
1112 for( i = 0; i < (int)[o_options count]; i++ )
1114 playlist_ItemAddOption( p_item, strdup( [[o_options objectAtIndex:i] UTF8String] ) );
1118 /* Recent documents menu */
1119 o_true_file = [NSURL fileURLWithPath: o_uri];
1120 if( o_true_file != nil )
1122 [[NSDocumentController sharedDocumentController]
1123 noteNewRecentDocumentURL: o_true_file];
1126 vlc_object_release( p_playlist );
1130 - (void)appendArray:(NSArray*)o_array atPos:(int)i_position enqueue:(BOOL)b_enqueue
1133 playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
1135 if( p_playlist == NULL )
1140 for( i_item = 0; i_item < (int)[o_array count]; i_item++ )
1142 playlist_item_t *p_item;
1143 NSDictionary *o_one_item;
1146 o_one_item = [o_array objectAtIndex: i_item];
1147 p_item = [self createItem: o_one_item];
1154 playlist_AddItem( p_playlist, p_item, PLAYLIST_INSERT, i_position == -1 ? PLAYLIST_END : i_position + i_item );
1156 if( i_item == 0 && !b_enqueue )
1158 playlist_Control( p_playlist, PLAYLIST_ITEMPLAY, p_item );
1161 vlc_object_release( p_playlist );
1164 - (void)appendNodeArray:(NSArray*)o_array inNode:(playlist_item_t *)p_node atPos:(int)i_position inView:(int)i_view enqueue:(BOOL)b_enqueue
1167 playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
1169 if( p_playlist == NULL )
1174 for( i_item = 0; i_item < (int)[o_array count]; i_item++ )
1176 playlist_item_t *p_item;
1177 NSDictionary *o_one_item;
1180 o_one_item = [o_array objectAtIndex: i_item];
1181 p_item = [self createItem: o_one_item];
1188 playlist_NodeAddItem( p_playlist, p_item, i_view, p_node, PLAYLIST_INSERT, i_position + i_item );
1190 if( i_item == 0 && !b_enqueue )
1192 playlist_Control( p_playlist, PLAYLIST_ITEMPLAY, p_item );
1195 vlc_object_release( p_playlist );
1199 - (IBAction)handlePopUp:(id)sender
1202 intf_thread_t * p_intf = VLCIntf;
1203 vlc_value_t val1,val2;
1204 playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
1206 if( p_playlist == NULL )
1211 switch( [o_loop_popup indexOfSelectedItem] )
1216 var_Set( p_playlist, "loop", val1 );
1218 var_Set( p_playlist, "repeat", val1 );
1219 vout_OSDMessage( p_intf, DEFAULT_CHAN, _( "Repeat One" ) );
1224 var_Set( p_playlist, "repeat", val1 );
1226 var_Set( p_playlist, "loop", val1 );
1227 vout_OSDMessage( p_intf, DEFAULT_CHAN, _( "Repeat All" ) );
1231 var_Get( p_playlist, "repeat", &val1 );
1232 var_Get( p_playlist, "loop", &val2 );
1233 if( val1.b_bool || val2.b_bool )
1236 var_Set( p_playlist, "repeat", val1 );
1237 var_Set( p_playlist, "loop", val1 );
1238 vout_OSDMessage( p_intf, DEFAULT_CHAN, _( "Repeat Off" ) );
1242 vlc_object_release( p_playlist );
1243 [self playlistUpdated];
1246 - (NSMutableArray *)subSearchItem:(playlist_item_t *)p_item
1248 playlist_t *p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
1250 playlist_item_t *p_selected_item;
1251 int i_current, i_selected_row;
1256 i_selected_row = [o_outline_view selectedRow];
1257 if (i_selected_row < 0)
1260 p_selected_item = (playlist_item_t *)[[o_outline_view itemAtRow:
1261 i_selected_row] pointerValue];
1263 for( i_current = 0; i_current < p_item->i_children ; i_current++ )
1266 NSString *o_current_name, *o_current_author;
1268 vlc_mutex_lock( &p_playlist->object_lock );
1269 o_current_name = [NSString stringWithUTF8String:
1270 p_item->pp_children[i_current]->input.psz_name];
1271 psz_temp = vlc_input_item_GetInfo( &p_item->input ,
1272 _("Meta-information"),_("Artist") );
1273 o_current_author = [NSString stringWithUTF8String: psz_temp];
1275 vlc_mutex_unlock( &p_playlist->object_lock );
1277 if( p_selected_item == p_item->pp_children[i_current] &&
1278 b_selected_item_met == NO )
1280 b_selected_item_met = YES;
1282 else if( p_selected_item == p_item->pp_children[i_current] &&
1283 b_selected_item_met == YES )
1285 vlc_object_release( p_playlist );
1288 else if( b_selected_item_met == YES &&
1289 ( [o_current_name rangeOfString:[o_search_field
1290 stringValue] options:NSCaseInsensitiveSearch ].length ||
1291 [o_current_author rangeOfString:[o_search_field
1292 stringValue] options:NSCaseInsensitiveSearch ].length ) )
1294 vlc_object_release( p_playlist );
1295 /*Adds the parent items in the result array as well, so that we can
1297 return [NSMutableArray arrayWithObject: [NSValue
1298 valueWithPointer: p_item->pp_children[i_current]]];
1300 if( p_item->pp_children[i_current]->i_children > 0 )
1302 id o_result = [self subSearchItem:
1303 p_item->pp_children[i_current]];
1304 if( o_result != NULL )
1306 vlc_object_release( p_playlist );
1307 [o_result insertObject: [NSValue valueWithPointer:
1308 p_item->pp_children[i_current]] atIndex:0];
1313 vlc_object_release( p_playlist );
1317 - (IBAction)searchItem:(id)sender
1319 playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
1321 playlist_view_t * p_view;
1327 b_selected_item_met = NO;
1329 if( p_playlist == NULL )
1331 p_view = playlist_ViewFind( p_playlist, i_current_view );
1335 /*First, only search after the selected item:*
1336 *(b_selected_item_met = NO) */
1337 o_result = [self subSearchItem:p_view->p_root];
1338 if( o_result == NULL )
1340 /* If the first search failed, search again from the beginning */
1341 o_result = [self subSearchItem:p_view->p_root];
1343 if( o_result != NULL )
1346 if( [[o_result objectAtIndex: 0] pointerValue] ==
1347 p_playlist->p_general )
1352 for( i = i_start ; i < [o_result count] - 1 ; i++ )
1354 [o_outline_view expandItem: [o_outline_dict objectForKey:
1355 [NSString stringWithFormat: @"%p",
1356 [[o_result objectAtIndex: i] pointerValue]]]];
1358 i_row = [o_outline_view rowForItem: [o_outline_dict objectForKey:
1359 [NSString stringWithFormat: @"%p",
1360 [[o_result objectAtIndex: [o_result count] - 1 ]
1365 [o_outline_view selectRow:i_row byExtendingSelection: NO];
1366 [o_outline_view scrollRowToVisible: i_row];
1369 vlc_object_release( p_playlist );
1372 - (IBAction)recursiveExpandNode:(id)sender
1375 id o_item = [o_outline_view itemAtRow: [o_outline_view selectedRow]];
1376 playlist_item_t *p_item = (playlist_item_t *)[o_item pointerValue];
1378 if( ![[o_outline_view dataSource] outlineView: o_outline_view
1379 isItemExpandable: o_item] )
1381 for( i = 0 ; i < p_item->i_parents ; i++ )
1383 if( p_item->pp_parents[i]->i_view == i_current_view )
1385 o_item = [o_outline_dict objectForKey: [NSString
1386 stringWithFormat: @"%p", p_item->pp_parents[i]->p_parent]];
1392 /* We need to collapse the node first, since OSX refuses to recursively
1393 expand an already expanded node, even if children nodes are collapsed. */
1394 [o_outline_view collapseItem: o_item collapseChildren: YES];
1395 [o_outline_view expandItem: o_item expandChildren: YES];
1398 - (NSMenu *)menuForEvent:(NSEvent *)o_event
1402 vlc_bool_t b_item_sel;
1404 pt = [o_outline_view convertPoint: [o_event locationInWindow]
1406 b_item_sel = ( [o_outline_view rowAtPoint: pt] != -1 &&
1407 [o_outline_view selectedRow] != -1 );
1408 b_rows = [o_outline_view numberOfRows] != 0;
1410 [o_mi_play setEnabled: b_item_sel];
1411 [o_mi_delete setEnabled: b_item_sel];
1412 [o_mi_selectall setEnabled: b_rows];
1413 [o_mi_info setEnabled: b_item_sel];
1414 [o_mi_preparse setEnabled: b_item_sel];
1415 [o_mi_recursive_expand setEnabled: b_item_sel];
1416 [o_mi_sort_name setEnabled: b_item_sel];
1417 [o_mi_sort_author setEnabled: b_item_sel];
1419 return( o_ctx_menu );
1422 - (void)outlineView: (NSTableView*)o_tv
1423 didClickTableColumn:(NSTableColumn *)o_tc
1425 int i_mode = 0, i_type;
1426 intf_thread_t *p_intf = VLCIntf;
1427 playlist_view_t *p_view;
1429 playlist_t *p_playlist = (playlist_t *)vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
1431 if( p_playlist == NULL )
1436 /* Check whether the selected table column header corresponds to a
1437 sortable table column*/
1438 if( !( o_tc == o_tc_name || o_tc == o_tc_author ) )
1440 vlc_object_release( p_playlist );
1444 p_view = playlist_ViewFind( p_playlist, i_current_view );
1446 if( o_tc_sortColumn == o_tc )
1448 b_isSortDescending = !b_isSortDescending;
1452 b_isSortDescending = VLC_FALSE;
1455 if( o_tc == o_tc_name )
1457 i_mode = SORT_TITLE;
1459 else if( o_tc == o_tc_author )
1461 i_mode = SORT_AUTHOR;
1464 if( b_isSortDescending )
1466 i_type = ORDER_REVERSE;
1470 i_type = ORDER_NORMAL;
1473 vlc_mutex_lock( &p_playlist->object_lock );
1474 playlist_RecursiveNodeSort( p_playlist, p_view->p_root, i_mode, i_type );
1475 vlc_mutex_unlock( &p_playlist->object_lock );
1477 vlc_object_release( p_playlist );
1478 [self playlistUpdated];
1480 o_tc_sortColumn = o_tc;
1481 [o_outline_view setHighlightedTableColumn:o_tc];
1483 if( b_isSortDescending )
1485 [o_outline_view setIndicatorImage:o_descendingSortingImage
1486 inTableColumn:o_tc];
1490 [o_outline_view setIndicatorImage:o_ascendingSortingImage
1491 inTableColumn:o_tc];
1496 - (void)outlineView:(NSOutlineView *)outlineView
1497 willDisplayCell:(id)cell
1498 forTableColumn:(NSTableColumn *)tableColumn
1501 playlist_t *p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
1506 if( !p_playlist ) return;
1508 o_playing_item = [o_outline_dict objectForKey:
1509 [NSString stringWithFormat:@"%p", p_playlist->status.p_item]];
1511 if( [self isItem: [o_playing_item pointerValue] inNode:
1512 [item pointerValue] checkItemExistence: YES]
1513 || [o_playing_item isEqual: item] )
1515 [cell setFont: [NSFont boldSystemFontOfSize: 0]];
1519 [cell setFont: [NSFont systemFontOfSize: 0]];
1521 vlc_object_release( p_playlist );
1524 - (IBAction)addNode:(id)sender
1526 /* simply adds a new node to the end of the playlist */
1527 playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
1534 playlist_item_t * p_item = playlist_NodeCreate( p_playlist, VIEW_CATEGORY,
1535 _("Empty Folder"), p_playlist->p_general );
1538 msg_Warn( VLCIntf, "node creation failed" );
1540 playlist_ViewUpdate( p_playlist, VIEW_CATEGORY );
1542 vlc_object_release( p_playlist );
1547 @implementation VLCPlaylist (NSOutlineViewDataSource)
1549 - (id)outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item
1551 id o_value = [super outlineView: outlineView child: index ofItem: item];
1552 playlist_t *p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
1555 if( !p_playlist ) return nil;
1557 if( p_playlist->i_size >= 2 )
1559 [o_status_field setStringValue: [NSString stringWithFormat:
1560 _NS("%i items in the playlist"), p_playlist->i_size]];
1564 if( p_playlist->i_size == 0 )
1566 [o_status_field setStringValue: _NS("No items in the playlist")];
1570 [o_status_field setStringValue: _NS("1 item in the playlist")];
1573 vlc_object_release( p_playlist );
1575 [o_outline_dict setObject:o_value forKey:[NSString stringWithFormat:@"%p",
1576 [o_value pointerValue]]];
1582 /* Required for drag & drop and reordering */
1583 - (BOOL)outlineView:(NSOutlineView *)outlineView writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pboard
1586 playlist_t *p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
1589 /* First remove the items that were moved during the last drag & drop
1591 [o_items_array removeAllObjects];
1592 [o_nodes_array removeAllObjects];
1594 if( !p_playlist ) return NO;
1596 for( i = 0 ; i < [items count] ; i++ )
1598 id o_item = [items objectAtIndex: i];
1600 /* Refuse to move items that are not in the General Node
1601 (Service Discovery) */
1602 if( ![self isItem: [o_item pointerValue] inNode:
1603 p_playlist->p_general checkItemExistence: NO])
1605 vlc_object_release(p_playlist);
1608 /* Fill the items and nodes to move in 2 different arrays */
1609 if( ((playlist_item_t *)[o_item pointerValue])->i_children > 0 )
1610 [o_nodes_array addObject: o_item];
1612 [o_items_array addObject: o_item];
1615 /* Now we need to check if there are selected items that are in already
1616 selected nodes. In that case, we only want to move the nodes */
1617 [self removeItemsFrom: o_nodes_array ifChildrenOf: o_nodes_array];
1618 [self removeItemsFrom: o_items_array ifChildrenOf: o_nodes_array];
1620 /* We add the "VLCPlaylistItemPboardType" type to be able to recognize
1621 a Drop operation coming from the playlist. */
1623 [pboard declareTypes: [NSArray arrayWithObjects:
1624 @"VLCPlaylistItemPboardType", nil] owner: self];
1625 [pboard setData:[NSData data] forType:@"VLCPlaylistItemPboardType"];
1627 vlc_object_release(p_playlist);
1631 - (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id <NSDraggingInfo>)info proposedItem:(id)item proposedChildIndex:(int)index
1633 playlist_t *p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
1635 NSPasteboard *o_pasteboard = [info draggingPasteboard];
1637 if( !p_playlist ) return NSDragOperationNone;
1639 /* Dropping ON items is not allowed if item is not a node */
1642 if( index == NSOutlineViewDropOnItemIndex &&
1643 ((playlist_item_t *)[item pointerValue])->i_children == -1 )
1645 vlc_object_release( p_playlist );
1646 return NSDragOperationNone;
1650 /* We refuse to drop an item in anything else than a child of the General
1651 Node. We still accept items that would be root nodes of the outlineview
1652 however, to allow drop in an empty playlist. */
1653 if( !([self isItem: [item pointerValue] inNode: p_playlist->p_general
1654 checkItemExistence: NO] || item == nil) )
1656 vlc_object_release( p_playlist );
1657 return NSDragOperationNone;
1660 /* Drop from the Playlist */
1661 if( [[o_pasteboard types] containsObject: @"VLCPlaylistItemPboardType"] )
1664 for( i = 0 ; i < [o_nodes_array count] ; i++ )
1666 /* We refuse to Drop in a child of an item we are moving */
1667 if( [self isItem: [item pointerValue] inNode:
1668 [[o_nodes_array objectAtIndex: i] pointerValue]
1669 checkItemExistence: NO] )
1671 vlc_object_release( p_playlist );
1672 return NSDragOperationNone;
1675 vlc_object_release(p_playlist);
1676 return NSDragOperationMove;
1679 /* Drop from the Finder */
1680 else if( [[o_pasteboard types] containsObject: NSFilenamesPboardType] )
1682 vlc_object_release(p_playlist);
1683 return NSDragOperationGeneric;
1685 vlc_object_release(p_playlist);
1686 return NSDragOperationNone;
1689 - (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id <NSDraggingInfo>)info item:(id)item childIndex:(int)index
1691 playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
1693 NSPasteboard *o_pasteboard = [info draggingPasteboard];
1695 if( !p_playlist ) return NO;
1697 /* Drag & Drop inside the playlist */
1698 if( [[o_pasteboard types] containsObject: @"VLCPlaylistItemPboardType"] )
1700 int i_row, i_removed_from_node = 0;
1702 playlist_item_t *p_new_parent, *p_item = NULL;
1703 NSArray *o_all_items = [o_nodes_array arrayByAddingObjectsFromArray:
1705 /* If the item is to be dropped as root item of the outline, make it a
1706 child of the General node.
1707 Else, choose the proposed parent as parent. */
1708 if( item == nil ) p_new_parent = p_playlist->p_general;
1709 else p_new_parent = [item pointerValue];
1711 /* Make sure the proposed parent is a node.
1712 (This should never be true) */
1713 if( p_new_parent->i_children < 0 )
1715 vlc_object_release( p_playlist );
1719 for( i = 0; i < [o_all_items count]; i++ )
1721 playlist_item_t *p_old_parent = NULL;
1722 int i_old_index = 0;
1724 p_item = [[o_all_items objectAtIndex:i] pointerValue];
1725 p_old_parent = [self parentOfItem: p_item];
1728 /* We may need the old index later */
1729 if( p_new_parent == p_old_parent )
1732 for( j = 0; j < p_old_parent->i_children; j++ )
1734 if( p_old_parent->pp_children[j] == p_item )
1742 vlc_mutex_lock( &p_playlist->object_lock );
1743 // Acually detach the item from the old position
1744 if( playlist_NodeRemoveItem( p_playlist, p_item, p_old_parent ) ==
1746 playlist_NodeRemoveParent( p_playlist, p_item, p_old_parent ) ==
1750 /* Calculate the new index */
1753 /* If we move the item in the same node, we need to take into
1754 account that one item will be deleted */
1757 if ((p_new_parent == p_old_parent &&
1758 i_old_index < index + (int)i) )
1760 i_removed_from_node++;
1762 i_new_index = index + i - i_removed_from_node;
1764 // Reattach the item to the new position
1765 playlist_NodeInsert( p_playlist, i_current_view, p_item,
1766 p_new_parent, i_new_index );
1768 vlc_mutex_unlock( &p_playlist->object_lock );
1770 [self playlistUpdated];
1771 i_row = [o_outline_view rowForItem:[o_outline_dict
1772 objectForKey:[NSString stringWithFormat: @"%p",
1773 [[o_all_items objectAtIndex: 0] pointerValue]]]];
1777 i_row = [o_outline_view rowForItem:[o_outline_dict
1778 objectForKey:[NSString stringWithFormat: @"%p", p_new_parent]]];
1781 [o_outline_view deselectAll: self];
1782 [o_outline_view selectRow: i_row byExtendingSelection: NO];
1783 [o_outline_view scrollRowToVisible: i_row];
1785 vlc_object_release(p_playlist);
1789 else if( [[o_pasteboard types] containsObject: NSFilenamesPboardType] )
1792 playlist_item_t *p_node = [item pointerValue];
1794 NSArray *o_array = [NSArray array];
1795 NSArray *o_values = [[o_pasteboard propertyListForType:
1796 NSFilenamesPboardType]
1797 sortedArrayUsingSelector:
1798 @selector(caseInsensitiveCompare:)];
1800 for( i = 0; i < (int)[o_values count]; i++)
1802 NSDictionary *o_dic;
1803 o_dic = [NSDictionary dictionaryWithObject:[o_values
1804 objectAtIndex:i] forKey:@"ITEM_URL"];
1805 o_array = [o_array arrayByAddingObject: o_dic];
1810 [self appendArray: o_array atPos: index enqueue: YES];
1812 /* This should never occur */
1813 else if( p_node->i_children == -1 )
1815 vlc_object_release( p_playlist );
1820 [self appendNodeArray: o_array inNode: p_node
1821 atPos: index inView: i_current_view enqueue: YES];
1823 vlc_object_release( p_playlist );
1826 vlc_object_release( p_playlist );
1830 /* Delegate method of NSWindow */
1831 /*- (void)windowWillClose:(NSNotification *)aNotification
1833 [o_btn_playlist setState: NSOffState];