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>
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 = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
119 i_current_view = VIEW_CATEGORY;
120 playlist_ViewUpdate( p_playlist, i_current_view );
122 [o_outline_view setTarget: self];
123 [o_outline_view setDelegate: self];
124 [o_outline_view setDataSource: self];
126 vlc_object_release( p_playlist );
132 [[o_tc_name headerCell] setStringValue:_NS("Name")];
133 [[o_tc_author headerCell] setStringValue:_NS("Author")];
134 [[o_tc_duration headerCell] setStringValue:_NS("Duration")];
137 - (NSOutlineView *)outlineView
139 return o_outline_view;
142 - (playlist_item_t *)selectedPlaylistItem
144 return [[o_outline_view itemAtRow: [o_outline_view selectedRow]]
150 @implementation VLCPlaylistCommon (NSOutlineViewDataSource)
152 /* return the number of children for Obj-C pointer item */ /* DONE */
153 - (int)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
156 playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
158 if( p_playlist == NULL )
160 if( outlineView != o_outline_view )
162 vlc_object_release( p_playlist );
169 playlist_view_t *p_view;
170 p_view = playlist_ViewFind( p_playlist, i_current_view );
171 if( p_view && p_view->p_root )
173 i_return = p_view->p_root->i_children;
175 if( i_current_view == VIEW_CATEGORY )
177 i_return--; /* remove the GENERAL item from the list */
178 i_return += p_playlist->p_general->i_children; /* add the items of the general node */
184 playlist_item_t *p_item = (playlist_item_t *)[item pointerValue];
186 i_return = p_item->i_children;
188 vlc_object_release( p_playlist );
196 /* return the child at index for the Obj-C pointer item */ /* DONE */
197 - (id)outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item
199 playlist_item_t *p_return = NULL;
200 playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
204 if( p_playlist == NULL )
210 playlist_view_t *p_view;
211 p_view = playlist_ViewFind( p_playlist, i_current_view );
212 if( p_view && p_view->p_root ) p_return = p_view->p_root->pp_children[index];
214 if( i_current_view == VIEW_CATEGORY )
216 if( p_playlist->p_general->i_children && index >= 0 && index < p_playlist->p_general->i_children )
218 p_return = p_playlist->p_general->pp_children[index];
220 else if( p_view && p_view->p_root && index >= 0 && index - p_playlist->p_general->i_children < p_view->p_root->i_children )
222 p_return = p_view->p_root->pp_children[index - p_playlist->p_general->i_children + 1];
228 playlist_item_t *p_item = (playlist_item_t *)[item pointerValue];
229 if( p_item && index < p_item->i_children && index >= 0 )
230 p_return = p_item->pp_children[index];
234 vlc_object_release( p_playlist );
236 o_value = [o_outline_dict objectForKey:[NSString stringWithFormat: @"%p", p_return]];
239 o_value = [[NSValue valueWithPointer: p_return] retain];
244 /* is the item expandable */
245 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
248 playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
250 if( p_playlist == NULL )
256 playlist_view_t *p_view;
257 p_view = playlist_ViewFind( p_playlist, i_current_view );
258 if( p_view && p_view->p_root ) i_return = p_view->p_root->i_children;
260 if( i_current_view == VIEW_CATEGORY )
263 i_return += p_playlist->p_general->i_children;
268 playlist_item_t *p_item = (playlist_item_t *)[item pointerValue];
270 i_return = p_item->i_children;
272 vlc_object_release( p_playlist );
280 /* retrieve the string values for the cells */
281 - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)o_tc byItem:(id)item
284 intf_thread_t *p_intf = VLCIntf;
285 playlist_t *p_playlist;
286 playlist_item_t *p_item;
288 if( item == nil || ![item isKindOfClass: [NSValue class]] ) return( @"error" );
290 p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
292 if( p_playlist == NULL )
297 p_item = (playlist_item_t *)[item pointerValue];
301 vlc_object_release( p_playlist );
305 if( [[o_tc identifier] isEqualToString:@"1"] )
307 o_value = [NSString stringWithUTF8String:
308 p_item->input.psz_name];
309 if( o_value == NULL )
310 o_value = [NSString stringWithCString:
311 p_item->input.psz_name];
313 else if( [[o_tc identifier] isEqualToString:@"2"] )
316 psz_temp = vlc_input_item_GetInfo( &p_item->input ,_("Meta-information"),_("Artist") );
318 if( psz_temp == NULL )
322 o_value = [NSString stringWithUTF8String: psz_temp];
323 if( o_value == NULL )
325 o_value = [NSString stringWithCString: psz_temp];
330 else if( [[o_tc identifier] isEqualToString:@"3"] )
332 char psz_duration[MSTRTIME_MAX_SIZE];
333 mtime_t dur = p_item->input.i_duration;
336 secstotimestr( psz_duration, dur/1000000 );
337 o_value = [NSString stringWithUTF8String: psz_duration];
341 o_value = @"-:--:--";
344 vlc_object_release( p_playlist );
351 /*****************************************************************************
352 * VLCPlaylistWizard implementation
353 *****************************************************************************/
354 @implementation VLCPlaylistWizard
356 - (IBAction)reloadOutlineView
358 /* Only reload the outlineview if the wizard window is open since this can
359 be quite long on big playlists */
360 if( [[o_outline_view window] isVisible] )
362 [o_outline_view reloadData];
368 /*****************************************************************************
369 * VLCPlaylist implementation
370 *****************************************************************************/
371 @implementation VLCPlaylist
378 o_nodes_array = [[NSMutableArray alloc] init];
379 o_items_array = [[NSMutableArray alloc] init];
386 playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
388 vlc_list_t *p_list = vlc_list_find( p_playlist, VLC_OBJECT_MODULE,
393 [super awakeFromNib];
395 [o_outline_view setDoubleAction: @selector(playItem:)];
397 [o_outline_view registerForDraggedTypes:
398 [NSArray arrayWithObjects: NSFilenamesPboardType,
399 @"VLCPlaylistItemPboardType", nil]];
400 [o_outline_view setIntercellSpacing: NSMakeSize (0.0, 1.0)];
402 /* We need to check whether _defaultTableHeaderSortImage exists, since it
403 belongs to an Apple hidden private API, and then can "disapear" at any time*/
405 if( [[NSOutlineView class] respondsToSelector:@selector(_defaultTableHeaderSortImage)] )
407 o_ascendingSortingImage = [[NSOutlineView class] _defaultTableHeaderSortImage];
411 o_ascendingSortingImage = nil;
414 if( [[NSOutlineView class] respondsToSelector:@selector(_defaultTableHeaderReverseSortImage)] )
416 o_descendingSortingImage = [[NSOutlineView class] _defaultTableHeaderReverseSortImage];
420 o_descendingSortingImage = nil;
423 o_tc_sortColumn = nil;
425 for( i_index = 0; i_index < p_list->i_count; i_index++ )
428 module_t * p_parser = (module_t *)p_list->p_values[i_index].p_object ;
430 if( !strcmp( p_parser->psz_capability, "services_discovery" ) )
432 /* create the menu entries used in the playlist menu */
433 o_lmi = [[o_mi_services submenu] addItemWithTitle:
434 [NSString stringWithUTF8String:
435 p_parser->psz_longname ? p_parser->psz_longname :
436 ( p_parser->psz_shortname ? p_parser->psz_shortname:
437 p_parser->psz_object_name)]
438 action: @selector(servicesChange:)
440 [o_lmi setTarget: self];
441 [o_lmi setRepresentedObject:
442 [NSString stringWithCString: p_parser->psz_object_name]];
443 if( playlist_IsServicesDiscoveryLoaded( p_playlist,
444 p_parser->psz_object_name ) )
445 [o_lmi setState: NSOnState];
447 /* create the menu entries for the main menu */
448 o_lmi = [[o_mm_mi_services submenu] addItemWithTitle:
449 [NSString stringWithUTF8String:
450 p_parser->psz_longname ? p_parser->psz_longname :
451 ( p_parser->psz_shortname ? p_parser->psz_shortname:
452 p_parser->psz_object_name)]
453 action: @selector(servicesChange:)
455 [o_lmi setTarget: self];
456 [o_lmi setRepresentedObject:
457 [NSString stringWithCString: p_parser->psz_object_name]];
458 if( playlist_IsServicesDiscoveryLoaded( p_playlist,
459 p_parser->psz_object_name ) )
460 [o_lmi setState: NSOnState];
463 vlc_list_release( p_list );
464 vlc_object_release( p_playlist );
466 /* Change the simple textfield into a searchField if we can... */
468 if( MACOS_VERSION >= 10.3 )
470 NSView *o_parentview = [o_status_field superview];
471 NSSearchField *o_better_search_field = [[NSSearchField alloc]initWithFrame:[o_search_field frame]];
472 [o_better_search_field setRecentsAutosaveName:@"VLC media player search"];
473 [o_better_search_field setDelegate:self];
474 [[NSNotificationCenter defaultCenter] addObserver: self
475 selector: @selector(searchfieldChanged:)
476 name: NSControlTextDidChangeNotification
477 object: o_better_search_field];
479 [o_better_search_field setTarget:self];
480 [o_better_search_field setAction:@selector(searchItem:)];
482 [o_better_search_field setAutoresizingMask:NSViewMinXMargin];
483 [o_parentview addSubview:o_better_search_field];
484 [o_search_field setHidden:YES];
487 //[self playlistUpdated];
490 - (void)searchfieldChanged:(NSNotification *)o_notification
492 [o_search_field setStringValue:[[o_notification object] stringValue]];
499 [o_mi_save_playlist setTitle: _NS("Save Playlist...")];
500 [o_mi_play setTitle: _NS("Play")];
501 [o_mi_delete setTitle: _NS("Delete")];
502 [o_mi_recursive_expand setTitle: _NS("Expand Node")];
503 [o_mi_selectall setTitle: _NS("Select All")];
504 [o_mi_info setTitle: _NS("Properties")];
505 [o_mi_preparse setTitle: _NS("Preparse")];
506 [o_mi_sort_name setTitle: _NS("Sort Node by Name")];
507 [o_mi_sort_author setTitle: _NS("Sort Node by Author")];
508 [o_mi_services setTitle: _NS("Services discovery")];
509 [o_status_field setStringValue: [NSString stringWithFormat:
510 _NS("No items in the playlist")]];
512 [o_random_ckb setTitle: _NS("Random")];
514 [o_search_button setTitle: _NS("Search")];
516 [o_search_field setToolTip: _NS("Search in Playlist")];
517 [[o_loop_popup itemAtIndex:0] setTitle: _NS("Standard Play")];
518 [[o_loop_popup itemAtIndex:1] setTitle: _NS("Repeat One")];
519 [[o_loop_popup itemAtIndex:2] setTitle: _NS("Repeat All")];
520 [o_mi_addNode setTitle: _NS("Add Folder to Playlist")];
523 - (void)playlistUpdated
527 /* Clear indications of any existing column sorting*/
528 for( i = 0 ; i < [[o_outline_view tableColumns] count] ; i++ )
530 [o_outline_view setIndicatorImage:nil inTableColumn:
531 [[o_outline_view tableColumns] objectAtIndex:i]];
534 [o_outline_view setHighlightedTableColumn:nil];
535 o_tc_sortColumn = nil;
536 // TODO Find a way to keep the dict size to a minimum
537 //[o_outline_dict removeAllObjects];
538 [o_outline_view reloadData];
539 [[[[VLCMain sharedInstance] getWizard] getPlaylistWizard] reloadOutlineView];
540 [[[[VLCMain sharedInstance] getBookmarks] getDataTable] reloadData];
543 - (void)playModeUpdated
545 playlist_t *p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
547 vlc_value_t val, val2;
549 if( p_playlist == NULL )
554 var_Get( p_playlist, "loop", &val2 );
555 var_Get( p_playlist, "repeat", &val );
556 if( val.b_bool == VLC_TRUE )
558 [o_loop_popup selectItemAtIndex: 1];
560 else if( val2.b_bool == VLC_TRUE )
562 [o_loop_popup selectItemAtIndex: 2];
566 [o_loop_popup selectItemAtIndex: 0];
569 var_Get( p_playlist, "random", &val );
570 [o_random_ckb setState: val.b_bool];
572 vlc_object_release( p_playlist );
575 - (playlist_item_t *)parentOfItem:(playlist_item_t *)p_item
578 for( i = 0 ; i < p_item->i_parents; i++ )
580 if( p_item->pp_parents[i]->i_view == i_current_view )
582 return p_item->pp_parents[i]->p_parent;
588 - (void)updateRowSelection
593 playlist_t *p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
595 playlist_item_t *p_item, *p_temp_item;
596 NSMutableArray *o_array = [NSMutableArray array];
598 if( p_playlist == NULL )
601 p_item = p_playlist->status.p_item;
604 vlc_object_release(p_playlist);
608 p_temp_item = p_item;
609 while( p_temp_item->i_parents > 0 )
611 [o_array insertObject: [NSValue valueWithPointer: p_temp_item] atIndex: 0];
613 p_temp_item = [self parentOfItem: p_temp_item];
614 /*for (i = 0 ; i < p_temp_item->i_parents ; i++)
616 if( p_temp_item->pp_parents[i]->i_view == i_current_view )
618 p_temp_item = p_temp_item->pp_parents[i]->p_parent;
624 for (j = 0 ; j < [o_array count] - 1 ; j++)
627 if( ( o_item = [o_outline_dict objectForKey:
628 [NSString stringWithFormat: @"%p",
629 [[o_array objectAtIndex:j] pointerValue]]] ) != nil )
630 [o_outline_view expandItem: o_item];
634 i_row = [o_outline_view rowForItem:[o_outline_dict
635 objectForKey:[NSString stringWithFormat: @"%p", p_item]]];
637 [o_outline_view selectRow: i_row byExtendingSelection: NO];
638 [o_outline_view scrollRowToVisible: i_row];
640 vlc_object_release(p_playlist);
643 /* Check if p_item is a child of p_node recursively. We need to check the item
644 existence first since OSX sometimes tries to redraw items that have been
645 deleted. We don't do it when not required since this verification takes
646 quite a long time on big playlists (yes, pretty hacky). */
647 - (BOOL)isItem: (playlist_item_t *)p_item
648 inNode: (playlist_item_t *)p_node
649 checkItemExistence:(BOOL)b_check
652 playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
654 playlist_item_t *p_temp_item = p_item;
656 if( p_playlist == NULL )
661 if( p_node == p_item )
663 vlc_object_release(p_playlist);
667 if( p_node->i_children < 1)
669 vlc_object_release(p_playlist);
676 vlc_mutex_lock( &p_playlist->object_lock );
680 /* Since outlineView: willDisplayCell:... may call this function with
681 p_items that don't exist anymore, first check if the item is still
682 in the playlist. Any cleaner solution welcomed. */
683 for( i = 0; i < p_playlist->i_all_size; i++ )
685 if( p_playlist->pp_all_items[i] == p_item ) break;
686 else if ( i == p_playlist->i_all_size - 1 )
688 vlc_object_release( p_playlist );
689 vlc_mutex_unlock( &p_playlist->object_lock );
695 while( p_temp_item->i_parents > 0 )
697 p_temp_item = [self parentOfItem: p_temp_item];
698 if( p_temp_item == p_node )
700 vlc_mutex_unlock( &p_playlist->object_lock );
701 vlc_object_release( p_playlist );
705 /* for( i = 0; i < p_temp_item->i_parents ; i++ )
707 if( p_temp_item->pp_parents[i]->i_view == i_current_view )
709 if( p_temp_item->pp_parents[i]->p_parent == p_node )
711 vlc_mutex_unlock( &p_playlist->object_lock );
712 vlc_object_release( p_playlist );
717 p_temp_item = p_temp_item->pp_parents[i]->p_parent;
723 vlc_mutex_unlock( &p_playlist->object_lock );
726 vlc_object_release( p_playlist );
730 /* This method is usefull for instance to remove the selected children of an
731 already selected node */
732 - (void)removeItemsFrom:(id)o_items ifChildrenOf:(id)o_nodes
735 for( i = 0 ; i < [o_items count] ; i++ )
737 for ( j = 0 ; j < [o_nodes count] ; j++ )
739 if( o_items == o_nodes)
741 if( j == i ) continue;
743 if( [self isItem: [[o_items objectAtIndex:i] pointerValue]
744 inNode: [[o_nodes objectAtIndex:j] pointerValue]
745 checkItemExistence: NO] )
747 [o_items removeObjectAtIndex:i];
748 /* We need to execute the next iteration with the same index
749 since the current item has been deleted */
758 - (IBAction)savePlaylist:(id)sender
760 intf_thread_t * p_intf = VLCIntf;
761 playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
764 NSSavePanel *o_save_panel = [NSSavePanel savePanel];
765 NSString * o_name = [NSString stringWithFormat: @"%@.m3u", _NS("Untitled")];
766 [o_save_panel setTitle: _NS("Save Playlist")];
767 [o_save_panel setPrompt: _NS("Save")];
769 if( [o_save_panel runModalForDirectory: nil
770 file: o_name] == NSOKButton )
772 playlist_Export( p_playlist, [[o_save_panel filename] fileSystemRepresentation], "export-m3u" );
774 vlc_object_release( p_playlist );
778 /* When called retrieves the selected outlineview row and plays that node or item */
779 - (IBAction)playItem:(id)sender
781 intf_thread_t * p_intf = VLCIntf;
782 playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
785 if( p_playlist != NULL )
787 playlist_item_t *p_item;
788 playlist_item_t *p_node = NULL;
790 p_item = [[o_outline_view itemAtRow:[o_outline_view selectedRow]] pointerValue];
794 if( p_item->i_children == -1 )
796 p_node = [self parentOfItem: p_item];
798 /* for( i = 0 ; i < p_item->i_parents ; i++ )
800 if( p_item->pp_parents[i]->i_view == i_current_view )
802 p_node = p_item->pp_parents[i]->p_parent;
809 if( p_node->i_children > 0 && p_node->pp_children[0]->i_children == -1 )
811 p_item = p_node->pp_children[0];
818 playlist_Control( p_playlist, PLAYLIST_VIEWPLAY, i_current_view, p_node, p_item );
820 vlc_object_release( p_playlist );
824 /* When called retrieves the selected outlineview row and plays that node or item */
825 - (IBAction)preparseItem:(id)sender
828 NSMutableArray *o_to_preparse;
829 intf_thread_t * p_intf = VLCIntf;
830 playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
833 o_to_preparse = [NSMutableArray arrayWithArray:[[o_outline_view selectedRowEnumerator] allObjects]];
834 i_count = [o_to_preparse count];
836 if( p_playlist != NULL )
840 playlist_item_t *p_item = NULL;
842 for( i = 0; i < i_count; i++ )
844 o_number = [o_to_preparse lastObject];
845 i_row = [o_number intValue];
846 p_item = [[o_outline_view itemAtRow:i_row] pointerValue];
847 [o_to_preparse removeObject: o_number];
848 [o_outline_view deselectRow: i_row];
852 if( p_item->i_children == -1 )
854 playlist_PreparseEnqueue( p_playlist, &p_item->input );
858 msg_Dbg( p_intf, "preparse of nodes not yet implemented" );
862 vlc_object_release( p_playlist );
864 [self playlistUpdated];
867 - (IBAction)servicesChange:(id)sender
869 NSMenuItem *o_mi = (NSMenuItem *)sender;
870 NSString *o_string = [o_mi representedObject];
871 playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
873 if( !playlist_IsServicesDiscoveryLoaded( p_playlist, [o_string cString] ) )
874 playlist_ServicesDiscoveryAdd( p_playlist, [o_string cString] );
876 playlist_ServicesDiscoveryRemove( p_playlist, [o_string cString] );
878 [o_mi setState: playlist_IsServicesDiscoveryLoaded( p_playlist,
879 [o_string cString] ) ? YES : NO];
881 i_current_view = VIEW_CATEGORY;
882 playlist_ViewUpdate( p_playlist, i_current_view );
883 vlc_object_release( p_playlist );
884 [self playlistUpdated];
888 - (IBAction)selectAll:(id)sender
890 [o_outline_view selectAll: nil];
893 - (IBAction)deleteItem:(id)sender
895 int i, i_count, i_row;
896 NSMutableArray *o_to_delete;
899 playlist_t * p_playlist;
900 intf_thread_t * p_intf = VLCIntf;
902 p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
905 if ( p_playlist == NULL )
909 o_to_delete = [NSMutableArray arrayWithArray:[[o_outline_view selectedRowEnumerator] allObjects]];
910 i_count = [o_to_delete count];
912 for( i = 0; i < i_count; i++ )
914 o_number = [o_to_delete lastObject];
915 i_row = [o_number intValue];
916 id o_item = [o_outline_view itemAtRow: i_row];
917 playlist_item_t *p_item = [o_item pointerValue];
918 [o_to_delete removeObject: o_number];
919 [o_outline_view deselectRow: i_row];
921 if( [[o_outline_view dataSource] outlineView:o_outline_view
922 numberOfChildrenOfItem: o_item] > 0 )
923 //is a node and not an item
925 if( p_playlist->status.i_status != PLAYLIST_STOPPED &&
926 [self isItem: p_playlist->status.p_item inNode:
927 ((playlist_item_t *)[o_item pointerValue])
928 checkItemExistence: NO] == YES )
930 // if current item is in selected node and is playing then stop playlist
931 playlist_Stop( p_playlist );
933 vlc_mutex_lock( &p_playlist->object_lock );
934 playlist_NodeDelete( p_playlist, p_item, VLC_TRUE, VLC_FALSE );
935 vlc_mutex_unlock( &p_playlist->object_lock );
939 if( p_playlist->status.i_status != PLAYLIST_STOPPED &&
940 p_playlist->status.p_item == [[o_outline_view itemAtRow: i_row] pointerValue] )
942 playlist_Stop( p_playlist );
944 vlc_mutex_lock( &p_playlist->object_lock );
945 playlist_Delete( p_playlist, p_item->input.i_id );
946 vlc_mutex_unlock( &p_playlist->object_lock );
949 [self playlistUpdated];
950 vlc_object_release( p_playlist );
953 - (IBAction)sortNodeByName:(id)sender
955 [self sortNode: SORT_TITLE];
958 - (IBAction)sortNodeByAuthor:(id)sender
960 [self sortNode: SORT_AUTHOR];
963 - (void)sortNode:(int)i_mode
965 playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
967 playlist_item_t * p_item;
969 if (p_playlist == NULL)
974 if( [o_outline_view selectedRow] > -1 )
976 p_item = [[o_outline_view itemAtRow: [o_outline_view selectedRow]]
980 /*If no item is selected, sort the whole playlist*/
982 playlist_view_t * p_view = playlist_ViewFind( p_playlist, i_current_view );
983 p_item = p_view->p_root;
986 if( p_item->i_children > -1 ) // the item is a node
988 vlc_mutex_lock( &p_playlist->object_lock );
989 playlist_RecursiveNodeSort( p_playlist, p_item, i_mode, ORDER_NORMAL );
990 vlc_mutex_unlock( &p_playlist->object_lock );
996 for( i = 0 ; i < p_item->i_parents ; i++ )
998 if( p_item->pp_parents[i]->i_view == i_current_view )
1000 vlc_mutex_lock( &p_playlist->object_lock );
1001 playlist_RecursiveNodeSort( p_playlist,
1002 p_item->pp_parents[i]->p_parent, i_mode, ORDER_NORMAL );
1003 vlc_mutex_unlock( &p_playlist->object_lock );
1008 vlc_object_release( p_playlist );
1009 [self playlistUpdated];
1012 - (playlist_item_t *)createItem:(NSDictionary *)o_one_item
1014 intf_thread_t * p_intf = VLCIntf;
1015 playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
1018 if( p_playlist == NULL )
1022 playlist_item_t *p_item;
1024 BOOL b_rem = FALSE, b_dir = FALSE;
1025 NSString *o_uri, *o_name;
1030 o_uri = (NSString *)[o_one_item objectForKey: @"ITEM_URL"];
1031 o_name = (NSString *)[o_one_item objectForKey: @"ITEM_NAME"];
1032 o_options = (NSArray *)[o_one_item objectForKey: @"ITEM_OPTIONS"];
1034 /* Find the name for a disc entry ( i know, can you believe the trouble?) */
1035 if( ( !o_name || [o_name isEqualToString:@""] ) && [o_uri rangeOfString: @"/dev/"].location != NSNotFound )
1037 int i_count, i_index;
1038 struct statfs *mounts = NULL;
1040 i_count = getmntinfo (&mounts, MNT_NOWAIT);
1041 /* getmntinfo returns a pointer to static data. Do not free. */
1042 for( i_index = 0 ; i_index < i_count; i_index++ )
1044 NSMutableString *o_temp, *o_temp2;
1045 o_temp = [NSMutableString stringWithString: o_uri];
1046 o_temp2 = [NSMutableString stringWithCString: mounts[i_index].f_mntfromname];
1047 [o_temp replaceOccurrencesOfString: @"/dev/rdisk" withString: @"/dev/disk" options:nil range:NSMakeRange(0, [o_temp length]) ];
1048 [o_temp2 replaceOccurrencesOfString: @"s0" withString: @"" options:nil range:NSMakeRange(0, [o_temp2 length]) ];
1049 [o_temp2 replaceOccurrencesOfString: @"s1" withString: @"" options:nil range:NSMakeRange(0, [o_temp2 length]) ];
1051 if( strstr( [o_temp fileSystemRepresentation], [o_temp2 fileSystemRepresentation] ) != NULL )
1053 o_name = [[NSFileManager defaultManager] displayNameAtPath: [NSString stringWithCString:mounts[i_index].f_mntonname]];
1057 /* If no name, then make a guess */
1058 if( !o_name) o_name = [[NSFileManager defaultManager] displayNameAtPath: o_uri];
1060 if( [[NSFileManager defaultManager] fileExistsAtPath:o_uri isDirectory:&b_dir] && b_dir &&
1061 [[NSWorkspace sharedWorkspace] getFileSystemInfoForPath: o_uri isRemovable: &b_rem
1062 isWritable:NULL isUnmountable:NULL description:NULL type:NULL] && b_rem )
1064 /* All of this is to make sure CD's play when you D&D them on VLC */
1065 /* Converts mountpoint to a /dev file */
1068 NSMutableString *o_temp;
1070 buf = (struct statfs *) malloc (sizeof(struct statfs));
1071 statfs( [o_uri fileSystemRepresentation], buf );
1072 psz_dev = strdup(buf->f_mntfromname);
1073 o_temp = [NSMutableString stringWithCString: psz_dev ];
1074 [o_temp replaceOccurrencesOfString: @"/dev/disk" withString: @"/dev/rdisk" options:nil range:NSMakeRange(0, [o_temp length]) ];
1075 [o_temp replaceOccurrencesOfString: @"s0" withString: @"" options:nil range:NSMakeRange(0, [o_temp length]) ];
1076 [o_temp replaceOccurrencesOfString: @"s1" withString: @"" options:nil range:NSMakeRange(0, [o_temp length]) ];
1080 p_item = playlist_ItemNew( p_intf, [o_uri fileSystemRepresentation], [o_name UTF8String] );
1086 for( i = 0; i < (int)[o_options count]; i++ )
1088 playlist_ItemAddOption( p_item, strdup( [[o_options objectAtIndex:i] UTF8String] ) );
1092 /* Recent documents menu */
1093 o_true_file = [NSURL fileURLWithPath: o_uri];
1094 if( o_true_file != nil )
1096 [[NSDocumentController sharedDocumentController]
1097 noteNewRecentDocumentURL: o_true_file];
1100 vlc_object_release( p_playlist );
1104 - (void)appendArray:(NSArray*)o_array atPos:(int)i_position enqueue:(BOOL)b_enqueue
1107 playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
1109 if( p_playlist == NULL )
1114 for( i_item = 0; i_item < (int)[o_array count]; i_item++ )
1116 playlist_item_t *p_item;
1117 NSDictionary *o_one_item;
1120 o_one_item = [o_array objectAtIndex: i_item];
1121 p_item = [self createItem: o_one_item];
1128 playlist_AddItem( p_playlist, p_item, PLAYLIST_INSERT, i_position == -1 ? PLAYLIST_END : i_position + i_item );
1130 if( i_item == 0 && !b_enqueue )
1132 playlist_Control( p_playlist, PLAYLIST_ITEMPLAY, p_item );
1135 vlc_object_release( p_playlist );
1138 - (void)appendNodeArray:(NSArray*)o_array inNode:(playlist_item_t *)p_node atPos:(int)i_position inView:(int)i_view enqueue:(BOOL)b_enqueue
1141 playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
1143 if( p_playlist == NULL )
1148 for( i_item = 0; i_item < (int)[o_array count]; i_item++ )
1150 playlist_item_t *p_item;
1151 NSDictionary *o_one_item;
1154 o_one_item = [o_array objectAtIndex: i_item];
1155 p_item = [self createItem: o_one_item];
1162 playlist_NodeAddItem( p_playlist, p_item, i_view, p_node, PLAYLIST_INSERT, i_position + i_item );
1164 if( i_item == 0 && !b_enqueue )
1166 playlist_Control( p_playlist, PLAYLIST_ITEMPLAY, p_item );
1169 vlc_object_release( p_playlist );
1173 - (IBAction)handlePopUp:(id)sender
1176 intf_thread_t * p_intf = VLCIntf;
1177 vlc_value_t val1,val2;
1178 playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
1180 if( p_playlist == NULL )
1185 switch( [o_loop_popup indexOfSelectedItem] )
1190 var_Set( p_playlist, "loop", val1 );
1192 var_Set( p_playlist, "repeat", val1 );
1193 vout_OSDMessage( p_intf, DEFAULT_CHAN, _( "Repeat One" ) );
1198 var_Set( p_playlist, "repeat", val1 );
1200 var_Set( p_playlist, "loop", val1 );
1201 vout_OSDMessage( p_intf, DEFAULT_CHAN, _( "Repeat All" ) );
1205 var_Get( p_playlist, "repeat", &val1 );
1206 var_Get( p_playlist, "loop", &val2 );
1207 if( val1.b_bool || val2.b_bool )
1210 var_Set( p_playlist, "repeat", val1 );
1211 var_Set( p_playlist, "loop", val1 );
1212 vout_OSDMessage( p_intf, DEFAULT_CHAN, _( "Repeat Off" ) );
1216 vlc_object_release( p_playlist );
1217 [self playlistUpdated];
1220 - (NSMutableArray *)subSearchItem:(playlist_item_t *)p_item
1222 playlist_t *p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
1224 playlist_item_t *p_selected_item;
1225 int i_current, i_selected_row;
1230 i_selected_row = [o_outline_view selectedRow];
1231 if (i_selected_row < 0)
1234 p_selected_item = (playlist_item_t *)[[o_outline_view itemAtRow:
1235 i_selected_row] pointerValue];
1237 for( i_current = 0; i_current < p_item->i_children ; i_current++ )
1240 NSString *o_current_name, *o_current_author;
1242 vlc_mutex_lock( &p_playlist->object_lock );
1243 o_current_name = [NSString stringWithUTF8String:
1244 p_item->pp_children[i_current]->input.psz_name];
1245 psz_temp = vlc_input_item_GetInfo( &p_item->input ,
1246 _("Meta-information"),_("Artist") );
1247 o_current_author = [NSString stringWithUTF8String: psz_temp];
1249 vlc_mutex_unlock( &p_playlist->object_lock );
1251 if( p_selected_item == p_item->pp_children[i_current] &&
1252 b_selected_item_met == NO )
1254 b_selected_item_met = YES;
1256 else if( p_selected_item == p_item->pp_children[i_current] &&
1257 b_selected_item_met == YES )
1259 vlc_object_release( p_playlist );
1262 else if( b_selected_item_met == YES &&
1263 ( [o_current_name rangeOfString:[o_search_field
1264 stringValue] options:NSCaseInsensitiveSearch ].length ||
1265 [o_current_author rangeOfString:[o_search_field
1266 stringValue] options:NSCaseInsensitiveSearch ].length ) )
1268 vlc_object_release( p_playlist );
1269 /*Adds the parent items in the result array as well, so that we can
1271 return [NSMutableArray arrayWithObject: [NSValue
1272 valueWithPointer: p_item->pp_children[i_current]]];
1274 if( p_item->pp_children[i_current]->i_children > 0 )
1276 id o_result = [self subSearchItem:
1277 p_item->pp_children[i_current]];
1278 if( o_result != NULL )
1280 vlc_object_release( p_playlist );
1281 [o_result insertObject: [NSValue valueWithPointer:
1282 p_item->pp_children[i_current]] atIndex:0];
1287 vlc_object_release( p_playlist );
1291 - (IBAction)searchItem:(id)sender
1293 playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
1295 playlist_view_t * p_view;
1301 b_selected_item_met = NO;
1303 if( p_playlist == NULL )
1305 p_view = playlist_ViewFind( p_playlist, i_current_view );
1309 /*First, only search after the selected item:*
1310 *(b_selected_item_met = NO) */
1311 o_result = [self subSearchItem:p_view->p_root];
1312 if( o_result == NULL )
1314 /* If the first search failed, search again from the beginning */
1315 o_result = [self subSearchItem:p_view->p_root];
1317 if( o_result != NULL )
1320 if( [[o_result objectAtIndex: 0] pointerValue] ==
1321 p_playlist->p_general )
1326 for( i = i_start ; i < [o_result count] - 1 ; i++ )
1328 [o_outline_view expandItem: [o_outline_dict objectForKey:
1329 [NSString stringWithFormat: @"%p",
1330 [[o_result objectAtIndex: i] pointerValue]]]];
1332 i_row = [o_outline_view rowForItem: [o_outline_dict objectForKey:
1333 [NSString stringWithFormat: @"%p",
1334 [[o_result objectAtIndex: [o_result count] - 1 ]
1339 [o_outline_view selectRow:i_row byExtendingSelection: NO];
1340 [o_outline_view scrollRowToVisible: i_row];
1343 vlc_object_release( p_playlist );
1346 - (IBAction)recursiveExpandNode:(id)sender
1349 id o_item = [o_outline_view itemAtRow: [o_outline_view selectedRow]];
1350 playlist_item_t *p_item = (playlist_item_t *)[o_item pointerValue];
1352 if( ![[o_outline_view dataSource] outlineView: o_outline_view
1353 isItemExpandable: o_item] )
1355 for( i = 0 ; i < p_item->i_parents ; i++ )
1357 if( p_item->pp_parents[i]->i_view == i_current_view )
1359 o_item = [o_outline_dict objectForKey: [NSString
1360 stringWithFormat: @"%p", p_item->pp_parents[i]->p_parent]];
1366 /* We need to collapse the node first, since OSX refuses to recursively
1367 expand an already expanded node, even if children nodes are collapsed. */
1368 [o_outline_view collapseItem: o_item collapseChildren: YES];
1369 [o_outline_view expandItem: o_item expandChildren: YES];
1372 - (NSMenu *)menuForEvent:(NSEvent *)o_event
1376 vlc_bool_t b_item_sel;
1378 pt = [o_outline_view convertPoint: [o_event locationInWindow]
1380 b_item_sel = ( [o_outline_view rowAtPoint: pt] != -1 &&
1381 [o_outline_view selectedRow] != -1 );
1382 b_rows = [o_outline_view numberOfRows] != 0;
1384 [o_mi_play setEnabled: b_item_sel];
1385 [o_mi_delete setEnabled: b_item_sel];
1386 [o_mi_selectall setEnabled: b_rows];
1387 [o_mi_info setEnabled: b_item_sel];
1388 [o_mi_preparse setEnabled: b_item_sel];
1389 [o_mi_recursive_expand setEnabled: b_item_sel];
1390 [o_mi_sort_name setEnabled: b_item_sel];
1391 [o_mi_sort_author setEnabled: b_item_sel];
1393 return( o_ctx_menu );
1396 - (void)outlineView: (NSTableView*)o_tv
1397 didClickTableColumn:(NSTableColumn *)o_tc
1399 int i_mode = 0, i_type;
1400 intf_thread_t *p_intf = VLCIntf;
1401 playlist_view_t *p_view;
1403 playlist_t *p_playlist = (playlist_t *)vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
1405 if( p_playlist == NULL )
1410 /* Check whether the selected table column header corresponds to a
1411 sortable table column*/
1412 if( !( o_tc == o_tc_name || o_tc == o_tc_author ) )
1414 vlc_object_release( p_playlist );
1418 p_view = playlist_ViewFind( p_playlist, i_current_view );
1420 if( o_tc_sortColumn == o_tc )
1422 b_isSortDescending = !b_isSortDescending;
1426 b_isSortDescending = VLC_FALSE;
1429 if( o_tc == o_tc_name )
1431 i_mode = SORT_TITLE;
1433 else if( o_tc == o_tc_author )
1435 i_mode = SORT_AUTHOR;
1438 if( b_isSortDescending )
1440 i_type = ORDER_REVERSE;
1444 i_type = ORDER_NORMAL;
1447 vlc_mutex_lock( &p_playlist->object_lock );
1448 playlist_RecursiveNodeSort( p_playlist, p_view->p_root, i_mode, i_type );
1449 vlc_mutex_unlock( &p_playlist->object_lock );
1451 vlc_object_release( p_playlist );
1452 [self playlistUpdated];
1454 o_tc_sortColumn = o_tc;
1455 [o_outline_view setHighlightedTableColumn:o_tc];
1457 if( b_isSortDescending )
1459 [o_outline_view setIndicatorImage:o_descendingSortingImage
1460 inTableColumn:o_tc];
1464 [o_outline_view setIndicatorImage:o_ascendingSortingImage
1465 inTableColumn:o_tc];
1470 - (void)outlineView:(NSOutlineView *)outlineView
1471 willDisplayCell:(id)cell
1472 forTableColumn:(NSTableColumn *)tableColumn
1475 playlist_t *p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
1480 if( !p_playlist ) return;
1482 o_playing_item = [o_outline_dict objectForKey:
1483 [NSString stringWithFormat:@"%p", p_playlist->status.p_item]];
1485 if( [self isItem: [o_playing_item pointerValue] inNode:
1486 [item pointerValue] checkItemExistence: YES]
1487 || [o_playing_item isEqual: item] )
1489 [cell setFont: [NSFont boldSystemFontOfSize: 0]];
1493 [cell setFont: [NSFont systemFontOfSize: 0]];
1495 vlc_object_release( p_playlist );
1498 - (IBAction)addNode:(id)sender
1500 /* simply adds a new node to the end of the playlist */
1501 playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
1505 msg_Err( VLCIntf, "Uh Oh! Unable to find playlist!" );
1509 playlist_item_t * p_item = playlist_NodeCreate( p_playlist, VIEW_CATEGORY,
1510 _("Empty Folder"), p_playlist->p_general );
1513 msg_Warn( VLCIntf, "node creation failed, fix VLC!" );
1515 playlist_ViewUpdate( p_playlist, VIEW_CATEGORY );
1517 vlc_object_release( p_playlist );
1522 @implementation VLCPlaylist (NSOutlineViewDataSource)
1524 - (id)outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item
1526 id o_value = [super outlineView: outlineView child: index ofItem: item];
1527 playlist_t *p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
1530 if( !p_playlist ) return nil;
1532 if( p_playlist->i_size >= 2 )
1534 [o_status_field setStringValue: [NSString stringWithFormat:
1535 _NS("%i items in the playlist"), p_playlist->i_size]];
1539 if( p_playlist->i_size == 0 )
1541 [o_status_field setStringValue: _NS("No items in the playlist")];
1545 [o_status_field setStringValue: _NS("1 item in the playlist")];
1548 vlc_object_release( p_playlist );
1550 [o_outline_dict setObject:o_value forKey:[NSString stringWithFormat:@"%p",
1551 [o_value pointerValue]]];
1557 /* Required for drag & drop and reordering */
1558 - (BOOL)outlineView:(NSOutlineView *)outlineView writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pboard
1561 playlist_t *p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
1564 /* First remove the items that were moved during the last drag & drop
1566 [o_items_array removeAllObjects];
1567 [o_nodes_array removeAllObjects];
1569 if( !p_playlist ) return NO;
1571 for( i = 0 ; i < [items count] ; i++ )
1573 id o_item = [items objectAtIndex: i];
1575 /* Refuse to move items that are not in the General Node
1576 (Service Discovery) */
1577 if( ![self isItem: [o_item pointerValue] inNode:
1578 p_playlist->p_general checkItemExistence: NO])
1580 vlc_object_release(p_playlist);
1583 /* Fill the items and nodes to move in 2 different arrays */
1584 if( ((playlist_item_t *)[o_item pointerValue])->i_children > 0 )
1585 [o_nodes_array addObject: o_item];
1587 [o_items_array addObject: o_item];
1590 /* Now we need to check if there are selected items that are in already
1591 selected nodes. In that case, we only want to move the nodes */
1592 [self removeItemsFrom: o_nodes_array ifChildrenOf: o_nodes_array];
1593 [self removeItemsFrom: o_items_array ifChildrenOf: o_nodes_array];
1595 /* We add the "VLCPlaylistItemPboardType" type to be able to recognize
1596 a Drop operation coming from the playlist. */
1598 [pboard declareTypes: [NSArray arrayWithObjects:
1599 @"VLCPlaylistItemPboardType", nil] owner: self];
1600 [pboard setData:[NSData data] forType:@"VLCPlaylistItemPboardType"];
1602 vlc_object_release(p_playlist);
1606 - (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id <NSDraggingInfo>)info proposedItem:(id)item proposedChildIndex:(int)index
1608 playlist_t *p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
1610 NSPasteboard *o_pasteboard = [info draggingPasteboard];
1612 if( !p_playlist ) return NSDragOperationNone;
1614 /* Dropping ON items is not allowed if item is not a node */
1617 if( index == NSOutlineViewDropOnItemIndex &&
1618 ((playlist_item_t *)[item pointerValue])->i_children == -1 )
1620 vlc_object_release( p_playlist );
1621 return NSDragOperationNone;
1625 /* We refuse to drop an item in anything else than a child of the General
1626 Node. We still accept items that would be root nodes of the outlineview
1627 however, to allow drop in an empty playlist. */
1628 if( !([self isItem: [item pointerValue] inNode: p_playlist->p_general
1629 checkItemExistence: NO] || item == nil) )
1631 vlc_object_release( p_playlist );
1632 return NSDragOperationNone;
1635 /* Drop from the Playlist */
1636 if( [[o_pasteboard types] containsObject: @"VLCPlaylistItemPboardType"] )
1639 for( i = 0 ; i < [o_nodes_array count] ; i++ )
1641 /* We refuse to Drop in a child of an item we are moving */
1642 if( [self isItem: [item pointerValue] inNode:
1643 [[o_nodes_array objectAtIndex: i] pointerValue]
1644 checkItemExistence: NO] )
1646 vlc_object_release( p_playlist );
1647 return NSDragOperationNone;
1650 vlc_object_release(p_playlist);
1651 return NSDragOperationMove;
1654 /* Drop from the Finder */
1655 else if( [[o_pasteboard types] containsObject: NSFilenamesPboardType] )
1657 vlc_object_release(p_playlist);
1658 return NSDragOperationGeneric;
1660 vlc_object_release(p_playlist);
1661 return NSDragOperationNone;
1664 - (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id <NSDraggingInfo>)info item:(id)item childIndex:(int)index
1666 playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
1668 NSPasteboard *o_pasteboard = [info draggingPasteboard];
1670 if( !p_playlist ) return NO;
1672 /* Drag & Drop inside the playlist */
1673 if( [[o_pasteboard types] containsObject: @"VLCPlaylistItemPboardType"] )
1675 int i_row, i_removed_from_node = 0;
1677 playlist_item_t *p_new_parent, *p_item = NULL;
1678 NSArray *o_all_items = [o_nodes_array arrayByAddingObjectsFromArray:
1680 /* If the item is to be dropped as root item of the outline, make it a
1681 child of the General node.
1682 Else, choose the proposed parent as parent. */
1683 if( item == nil ) p_new_parent = p_playlist->p_general;
1684 else p_new_parent = [item pointerValue];
1686 /* Make sure the proposed parent is a node.
1687 (This should never be true) */
1688 if( p_new_parent->i_children < 0 )
1690 vlc_object_release( p_playlist );
1694 for( i = 0; i < [o_all_items count]; i++ )
1696 playlist_item_t *p_old_parent = NULL;
1697 int i_old_index = 0;
1699 p_item = [[o_all_items objectAtIndex:i] pointerValue];
1700 p_old_parent = [self parentOfItem: p_item];
1703 /* We may need the old index later */
1704 if( p_new_parent == p_old_parent )
1707 for( j = 0; j < p_old_parent->i_children; j++ )
1709 if( p_old_parent->pp_children[j] == p_item )
1717 vlc_mutex_lock( &p_playlist->object_lock );
1718 // Acually detach the item from the old position
1719 if( playlist_NodeRemoveItem( p_playlist, p_item, p_old_parent ) ==
1721 playlist_NodeRemoveParent( p_playlist, p_item, p_old_parent ) ==
1725 /* Calculate the new index */
1728 /* If we move the item in the same node, we need to take into
1729 account that one item will be deleted */
1732 if ((p_new_parent == p_old_parent &&
1733 i_old_index < index + (int)i) )
1735 i_removed_from_node++;
1737 i_new_index = index + i - i_removed_from_node;
1739 // Reattach the item to the new position
1740 playlist_NodeInsert( p_playlist, i_current_view, p_item,
1741 p_new_parent, i_new_index );
1743 vlc_mutex_unlock( &p_playlist->object_lock );
1745 [self playlistUpdated];
1746 i_row = [o_outline_view rowForItem:[o_outline_dict
1747 objectForKey:[NSString stringWithFormat: @"%p",
1748 [[o_all_items objectAtIndex: 0] pointerValue]]]];
1752 i_row = [o_outline_view rowForItem:[o_outline_dict
1753 objectForKey:[NSString stringWithFormat: @"%p", p_new_parent]]];
1756 [o_outline_view deselectAll: self];
1757 [o_outline_view selectRow: i_row byExtendingSelection: NO];
1758 [o_outline_view scrollRowToVisible: i_row];
1760 vlc_object_release(p_playlist);
1764 else if( [[o_pasteboard types] containsObject: NSFilenamesPboardType] )
1767 playlist_item_t *p_node = [item pointerValue];
1769 NSArray *o_array = [NSArray array];
1770 NSArray *o_values = [[o_pasteboard propertyListForType:
1771 NSFilenamesPboardType]
1772 sortedArrayUsingSelector:
1773 @selector(caseInsensitiveCompare:)];
1775 for( i = 0; i < (int)[o_values count]; i++)
1777 NSDictionary *o_dic;
1778 o_dic = [NSDictionary dictionaryWithObject:[o_values
1779 objectAtIndex:i] forKey:@"ITEM_URL"];
1780 o_array = [o_array arrayByAddingObject: o_dic];
1785 [self appendArray: o_array atPos: index enqueue: YES];
1787 /* This should never occur */
1788 else if( p_node->i_children == -1 )
1790 vlc_object_release( p_playlist );
1795 [self appendNodeArray: o_array inNode: p_node
1796 atPos: index inView: i_current_view enqueue: YES];
1798 vlc_object_release( p_playlist );
1801 vlc_object_release( p_playlist );
1805 /* Delegate method of NSWindow */
1806 /*- (void)windowWillClose:(NSNotification *)aNotification
1808 [o_btn_playlist setState: NSOffState];