1 /*****************************************************************************
2 * playlist.m: MacOS X interface module
3 *****************************************************************************
4 * Copyright (C) 2002-2012 VLC authors and VideoLAN
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>
10 * Felix Paul Kühne <fkuehne at videolan dot org>
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
25 *****************************************************************************/
28 * add 'icons' for different types of nodes? (http://www.cocoadev.com/index.pl?IconAndTextInTableCell)
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>
47 #import "playlistinfo.h"
53 #import <vlc_services_discovery.h>
55 #import <vlc_interface.h>
60 /*****************************************************************************
61 * VLCPlaylistView implementation
62 *****************************************************************************/
63 @implementation VLCPlaylistView
65 - (NSMenu *)menuForEvent:(NSEvent *)o_event
67 return( [(VLCPlaylist *)[self delegate] menuForEvent: o_event] );
70 - (void)keyDown:(NSEvent *)o_event
74 if( [[o_event characters] length] )
76 key = [[o_event characters] characterAtIndex: 0];
81 case NSDeleteCharacter:
82 case NSDeleteFunctionKey:
83 case NSDeleteCharFunctionKey:
84 case NSBackspaceCharacter:
85 [(VLCPlaylist *)[self delegate] deleteItem:self];
88 case NSEnterCharacter:
89 case NSCarriageReturnCharacter:
90 [(VLCPlaylist *)[[VLCMain sharedInstance] playlist] playItem:self];
94 [super keyDown: o_event];
101 /*****************************************************************************
102 * VLCPlaylistCommon implementation
104 * This class the superclass of the VLCPlaylist and VLCPlaylistWizard.
105 * It contains the common methods and elements of these 2 entities.
106 *****************************************************************************/
107 @implementation VLCPlaylistCommon
111 playlist_t * p_playlist = pl_Get( VLCIntf );
112 p_current_root_item = p_playlist->p_local_category;
117 o_outline_dict = [[NSMutableDictionary alloc] init];
124 playlist_t * p_playlist = pl_Get( VLCIntf );
125 [o_outline_view setTarget: self];
126 [o_outline_view setDelegate: self];
127 [o_outline_view setDataSource: self];
128 [o_outline_view setAllowsEmptySelection: NO];
129 [o_outline_view expandItem: [o_outline_view itemAtRow:0]];
131 [o_outline_view_other setTarget: self];
132 [o_outline_view_other setDelegate: self];
133 [o_outline_view_other setDataSource: self];
134 [o_outline_view_other setAllowsEmptySelection: NO];
141 [[o_tc_name headerCell] setStringValue:_NS("Name")];
142 [[o_tc_author headerCell] setStringValue:_NS("Author")];
143 [[o_tc_duration headerCell] setStringValue:_NS("Duration")];
145 [[o_tc_name_other headerCell] setStringValue:_NS("Name")];
146 [[o_tc_author_other headerCell] setStringValue:_NS("Author")];
147 [[o_tc_duration_other headerCell] setStringValue:_NS("Duration")];
150 - (void)setPlaylistRoot: (playlist_item_t *)root_item
152 p_current_root_item = root_item;
153 [o_outline_view reloadData];
154 [o_outline_view_other reloadData];
157 - (playlist_item_t *)currentPlaylistRoot
159 return p_current_root_item;
162 - (void)swapPlaylists:(id)newList
164 if(newList != o_outline_view)
166 id o_outline_view_temp = o_outline_view;
167 id o_tc_author_temp = o_tc_author;
168 id o_tc_duration_temp = o_tc_duration;
169 id o_tc_name_temp = o_tc_name;
170 o_outline_view = o_outline_view_other;
171 o_tc_author = o_tc_author_other;
172 o_tc_duration = o_tc_duration_other;
173 o_tc_name = o_tc_name_other;
174 o_outline_view_other = o_outline_view_temp;
175 o_tc_author_other = o_tc_author_temp;
176 o_tc_duration_other = o_tc_duration_temp;
177 o_tc_name_other = o_tc_name_temp;
181 - (NSOutlineView *)outlineView
183 return o_outline_view;
186 - (playlist_item_t *)selectedPlaylistItem
188 return [[o_outline_view itemAtRow: [o_outline_view selectedRow]]
194 @implementation VLCPlaylistCommon (NSOutlineViewDataSource)
195 /* return the number of children for Obj-C pointer item */ /* DONE */
196 - (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
199 playlist_item_t *p_item = NULL;
200 playlist_t * p_playlist = pl_Get( VLCIntf );
201 //assert( outlineView == o_outline_view );
206 p_item = p_current_root_item;
209 p_item = (playlist_item_t *)[item pointerValue];
212 i_return = p_item->i_children;
215 return i_return > 0 ? i_return : 0;
218 /* return the child at index for the Obj-C pointer item */ /* DONE */
219 - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item
221 playlist_item_t *p_return = NULL, *p_item = NULL;
223 playlist_t * p_playlist = pl_Get( VLCIntf );
229 p_item = p_current_root_item;
233 p_item = (playlist_item_t *)[item pointerValue];
235 if( p_item && index < p_item->i_children && index >= 0 )
236 p_return = p_item->pp_children[index];
239 o_value = [o_outline_dict objectForKey:[NSString stringWithFormat: @"%p", p_return]];
243 /* FIXME: Why is there a warning if that happens all the time and seems
244 * to be normal? Add an assert and fix it.
245 * msg_Warn( VLCIntf, "playlist item misses pointer value, adding one" ); */
246 o_value = [[NSValue valueWithPointer: p_return] retain];
251 /* is the item expandable */
252 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
255 playlist_t *p_playlist = pl_Get( VLCIntf );
261 if( p_current_root_item )
263 i_return = p_current_root_item->i_children;
268 playlist_item_t *p_item = (playlist_item_t *)[item pointerValue];
270 i_return = p_item->i_children;
274 return (i_return >= 0);
277 /* retrieve the string values for the cells */
278 - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)o_tc byItem:(id)item
281 playlist_item_t *p_item;
283 /* For error handling */
284 static BOOL attempted_reload = NO;
286 if( item == nil || ![item isKindOfClass: [NSValue class]] )
288 /* Attempt to fix the error by asking for a data redisplay
289 * This might cause infinite loop, so add a small check */
290 if( !attempted_reload )
292 attempted_reload = YES;
293 [outlineView reloadData];
298 p_item = (playlist_item_t *)[item pointerValue];
299 if( !p_item || !p_item->p_input )
301 /* Attempt to fix the error by asking for a data redisplay
302 * This might cause infinite loop, so add a small check */
303 if( !attempted_reload )
305 attempted_reload = YES;
306 [outlineView reloadData];
311 attempted_reload = NO;
313 if( [[o_tc identifier] isEqualToString:@"name"] )
315 /* sanity check to prevent the NSString class from crashing */
316 char *psz_title = input_item_GetTitleFbName( p_item->p_input );
319 o_value = [NSString stringWithUTF8String: psz_title];
323 else if( [[o_tc identifier] isEqualToString:@"artist"] )
325 char *psz_artist = input_item_GetArtist( p_item->p_input );
327 o_value = [NSString stringWithUTF8String: psz_artist];
330 else if( [[o_tc identifier] isEqualToString:@"duration"] )
332 char psz_duration[MSTRTIME_MAX_SIZE];
333 mtime_t dur = input_item_GetDuration( p_item->p_input );
336 secstotimestr( psz_duration, dur/1000000 );
337 o_value = [NSString stringWithUTF8String: psz_duration];
342 else if( [[o_tc identifier] isEqualToString:@"status"] )
344 if( input_item_HasErrorWhenReading( p_item->p_input ) )
346 o_value = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kAlertCautionIcon)];
347 [o_value setSize: NSMakeSize(16,16)];
355 /*****************************************************************************
356 * VLCPlaylistWizard implementation
357 *****************************************************************************/
358 @implementation VLCPlaylistWizard
360 - (IBAction)reloadOutlineView
362 /* Only reload the outlineview if the wizard window is open since this can
363 be quite long on big playlists */
364 if( [[o_outline_view window] isVisible] )
366 [o_outline_view reloadData];
372 /*****************************************************************************
373 * extension to NSOutlineView's interface to fix compilation warnings
374 * and let us access these 2 functions properly
375 * this uses a private Apple-API, but works fine on all current OSX releases
376 * keep checking for compatiblity with future releases though
377 *****************************************************************************/
379 @interface NSOutlineView (UndocumentedSortImages)
380 + (NSImage *)_defaultTableHeaderSortImage;
381 + (NSImage *)_defaultTableHeaderReverseSortImage;
385 /*****************************************************************************
386 * VLCPlaylist implementation
387 *****************************************************************************/
388 @implementation VLCPlaylist
395 o_nodes_array = [[NSMutableArray alloc] init];
396 o_items_array = [[NSMutableArray alloc] init];
403 [o_nodes_array release];
404 [o_items_array release];
410 playlist_t * p_playlist = pl_Get( VLCIntf );
414 [super awakeFromNib];
416 [o_outline_view setDoubleAction: @selector(playItem:)];
417 [o_outline_view_other setDoubleAction: @selector(playItem:)];
419 [o_outline_view registerForDraggedTypes:
420 [NSArray arrayWithObjects: NSFilenamesPboardType,
421 @"VLCPlaylistItemPboardType", nil]];
422 [o_outline_view setIntercellSpacing: NSMakeSize (0.0, 1.0)];
424 [o_outline_view_other registerForDraggedTypes:
425 [NSArray arrayWithObjects: NSFilenamesPboardType,
426 @"VLCPlaylistItemPboardType", nil]];
427 [o_outline_view_other setIntercellSpacing: NSMakeSize (0.0, 1.0)];
429 /* This uses private Apple API which works fine until 10.5.
430 * We need to keep checking in the future!
431 * These methods are being added artificially to NSOutlineView's interface above */
432 o_ascendingSortingImage = [[NSOutlineView class] _defaultTableHeaderSortImage];
433 o_descendingSortingImage = [[NSOutlineView class] _defaultTableHeaderReverseSortImage];
435 o_tc_sortColumn = nil;
438 char ** ppsz_services = vlc_sd_GetNames( VLCIntf, &ppsz_name, NULL );
442 for( i = 0; ppsz_services[i]; i++ )
447 char * name = ppsz_name[i] ? ppsz_name[i] : ppsz_services[i];
448 /* Check whether to enable these menuitems */
449 b_enabled = playlist_IsServicesDiscoveryLoaded( p_playlist, ppsz_services[i] );
451 /* Create the menu entries used in the playlist menu */
452 o_lmi = [[o_mi_services submenu] addItemWithTitle:
453 [NSString stringWithUTF8String: name]
454 action: @selector(servicesChange:)
456 [o_lmi setTarget: self];
457 [o_lmi setRepresentedObject: [NSString stringWithUTF8String: ppsz_services[i]]];
458 if( b_enabled ) [o_lmi setState: NSOnState];
460 /* Create the menu entries for the main menu */
461 o_lmi = [[o_mm_mi_services submenu] addItemWithTitle:
462 [NSString stringWithUTF8String: name]
463 action: @selector(servicesChange:)
465 [o_lmi setTarget: self];
466 [o_lmi setRepresentedObject: [NSString stringWithUTF8String: ppsz_services[i]]];
467 if( b_enabled ) [o_lmi setState: NSOnState];
469 free( ppsz_services[i] );
470 free( ppsz_name[i] );
472 free( ppsz_services );
477 - (void)searchfieldChanged:(NSNotification *)o_notification
479 [o_search_field setStringValue:[[o_notification object] stringValue]];
486 [o_mi_save_playlist setTitle: _NS("Save Playlist...")];
487 [o_mi_play setTitle: _NS("Play")];
488 [o_mi_delete setTitle: _NS("Delete")];
489 [o_mi_recursive_expand setTitle: _NS("Expand Node")];
490 [o_mi_selectall setTitle: _NS("Select All")];
491 [o_mi_info setTitle: _NS("Media Information...")];
492 [o_mi_dl_cover_art setTitle: _NS("Download Cover Art")];
493 [o_mi_preparse setTitle: _NS("Fetch Meta Data")];
494 [o_mi_revealInFinder setTitle: _NS("Reveal in Finder")];
495 [o_mm_mi_revealInFinder setTitle: _NS("Reveal in Finder")];
496 [[o_mm_mi_revealInFinder menu] setAutoenablesItems: NO];
497 [o_mi_sort_name setTitle: _NS("Sort Node by Name")];
498 [o_mi_sort_author setTitle: _NS("Sort Node by Author")];
499 [o_mi_services setTitle: _NS("Services discovery")];
500 [o_mm_mi_services setTitle: _NS("Services discovery")];
502 [o_search_field setToolTip: _NS("Search in Playlist")];
503 [o_search_field_other setToolTip: _NS("Search in Playlist")];
505 [o_save_accessory_text setStringValue: _NS("File Format:")];
506 [[o_save_accessory_popup itemAtIndex:0] setTitle: _NS("Extended M3U")];
507 [[o_save_accessory_popup itemAtIndex:1] setTitle: _NS("XML Shareable Playlist Format (XSPF)")];
508 [[o_save_accessory_popup itemAtIndex:2] setTitle: _NS("HTML Playlist")];
511 - (void)swapPlaylists:(id)newList
513 if(newList != o_outline_view)
515 id o_search_field_temp = o_search_field;
516 o_search_field = o_search_field_other;
517 o_search_field_other = o_search_field_temp;
518 [super swapPlaylists:newList];
519 [self playlistUpdated];
523 - (void)playlistUpdated
525 /* Clear indications of any existing column sorting */
526 NSUInteger count = [[o_outline_view tableColumns] count];
527 for( NSUInteger i = 0 ; i < count ; i++ )
529 [o_outline_view setIndicatorImage:nil inTableColumn:
530 [[o_outline_view tableColumns] objectAtIndex:i]];
533 [o_outline_view setHighlightedTableColumn:nil];
534 o_tc_sortColumn = nil;
535 // TODO Find a way to keep the dict size to a minimum
536 //[o_outline_dict removeAllObjects];
537 [o_outline_view reloadData];
538 [[[[VLCMain sharedInstance] wizard] playlistWizard] reloadOutlineView];
539 [[[[VLCMain sharedInstance] bookmarks] dataTable] reloadData];
541 [self outlineViewSelectionDidChange: nil];
542 [[VLCMain sharedInstance] updateMainWindow];
545 - (void)outlineViewSelectionDidChange:(NSNotification *)notification
548 playlist_item_t * p_item = [[o_outline_view itemAtRow:[o_outline_view selectedRow]] pointerValue];
552 /* update the state of our Reveal-in-Finder menu items */
553 NSMutableString *o_mrl;
554 char *psz_uri = input_item_GetURI( p_item->p_input );
556 [o_mi_revealInFinder setEnabled: NO];
557 [o_mm_mi_revealInFinder setEnabled: NO];
560 o_mrl = [NSMutableString stringWithUTF8String: psz_uri];
562 /* perform some checks whether it is a file and if it is local at all... */
563 NSRange prefix_range = [o_mrl rangeOfString: @"file:"];
564 if( prefix_range.location != NSNotFound )
565 [o_mrl deleteCharactersInRange: prefix_range];
567 if( [o_mrl characterAtIndex:0] == '/' )
569 [o_mi_revealInFinder setEnabled: YES];
570 [o_mm_mi_revealInFinder setEnabled: YES];
575 /* update our info-panel to reflect the new item */
576 [[[VLCMain sharedInstance] info] updatePanelWithItem:p_item->p_input];
580 - (BOOL)isSelectionEmpty
582 return [o_outline_view selectedRow] == -1;
585 - (void)updateRowSelection
588 playlist_t *p_playlist = pl_Get( VLCIntf );
589 playlist_item_t *p_item, *p_temp_item;
590 NSMutableArray *o_array = [NSMutableArray array];
593 p_item = playlist_CurrentPlayingItem( p_playlist );
600 p_temp_item = p_item;
601 while( p_temp_item->p_parent )
603 [o_array insertObject: [NSValue valueWithPointer: p_temp_item] atIndex: 0];
604 p_temp_item = p_temp_item->p_parent;
608 NSUInteger count = [o_array count];
609 for( NSUInteger j = 0; j < count - 1; j++ )
612 if( ( o_item = [o_outline_dict objectForKey:
613 [NSString stringWithFormat: @"%p",
614 [[o_array objectAtIndex:j] pointerValue]]] ) != nil )
616 [o_outline_view expandItem: o_item];
620 id o_item = [o_outline_dict objectForKey:[NSString stringWithFormat: @"%p", p_item]];
621 NSInteger i_index = [o_outline_view rowForItem:o_item];
622 [o_outline_view selectRowIndexes:[NSIndexSet indexSetWithIndex:i_index] byExtendingSelection:NO];
625 /* Check if p_item is a child of p_node recursively. We need to check the item
626 existence first since OSX sometimes tries to redraw items that have been
627 deleted. We don't do it when not required since this verification takes
628 quite a long time on big playlists (yes, pretty hacky). */
630 - (BOOL)isItem: (playlist_item_t *)p_item
631 inNode: (playlist_item_t *)p_node
632 checkItemExistence:(BOOL)b_check
633 locked:(BOOL)b_locked
636 playlist_t * p_playlist = pl_Get( VLCIntf );
637 playlist_item_t *p_temp_item = p_item;
639 if( p_node == p_item )
642 if( p_node->i_children < 1)
648 if(!b_locked) PL_LOCK;
652 /* Since outlineView: willDisplayCell:... may call this function with
653 p_items that don't exist anymore, first check if the item is still
654 in the playlist. Any cleaner solution welcomed. */
655 for( i = 0; i < p_playlist->all_items.i_size; i++ )
657 if( ARRAY_VAL( p_playlist->all_items, i) == p_item ) break;
658 else if ( i == p_playlist->all_items.i_size - 1 )
660 if(!b_locked) PL_UNLOCK;
668 p_temp_item = p_temp_item->p_parent;
669 if( p_temp_item == p_node )
671 if(!b_locked) PL_UNLOCK;
675 if(!b_locked) PL_UNLOCK;
680 - (BOOL)isItem: (playlist_item_t *)p_item
681 inNode: (playlist_item_t *)p_node
682 checkItemExistence:(BOOL)b_check
684 return [self isItem:p_item inNode:p_node checkItemExistence:b_check locked:NO];
687 /* This method is useful for instance to remove the selected children of an
688 already selected node */
689 - (void)removeItemsFrom:(id)o_items ifChildrenOf:(id)o_nodes
691 NSUInteger itemCount = [o_items count];
692 NSUInteger nodeCount = [o_nodes count];
693 for( NSUInteger i = 0 ; i < itemCount ; i++ )
695 for ( NSUInteger j = 0 ; j < nodeCount ; j++ )
697 if( o_items == o_nodes)
699 if( j == i ) continue;
701 if( [self isItem: [[o_items objectAtIndex:i] pointerValue]
702 inNode: [[o_nodes objectAtIndex:j] pointerValue]
703 checkItemExistence: NO locked:NO] )
705 [o_items removeObjectAtIndex:i];
706 /* We need to execute the next iteration with the same index
707 since the current item has been deleted */
715 - (IBAction)savePlaylist:(id)sender
717 playlist_t * p_playlist = pl_Get( VLCIntf );
719 NSSavePanel *o_save_panel = [NSSavePanel savePanel];
720 NSString * o_name = [NSString stringWithFormat: @"%@", _NS("Untitled")];
722 [o_save_panel setTitle: _NS("Save Playlist")];
723 [o_save_panel setPrompt: _NS("Save")];
724 [o_save_panel setAccessoryView: o_save_accessory_view];
726 if( [o_save_panel runModalForDirectory: nil
727 file: o_name] == NSOKButton )
729 NSString *o_filename = [[o_save_panel URL] path];
731 if( [o_save_accessory_popup indexOfSelectedItem] == 0 )
733 NSString * o_real_filename;
735 range.location = [o_filename length] - [@".m3u" length];
736 range.length = [@".m3u" length];
738 if( [o_filename compare:@".m3u" options: NSCaseInsensitiveSearch
739 range: range] != NSOrderedSame )
741 o_real_filename = [NSString stringWithFormat: @"%@.m3u", o_filename];
745 o_real_filename = o_filename;
747 playlist_Export( p_playlist,
748 [o_real_filename fileSystemRepresentation],
749 p_playlist->p_local_category, "export-m3u" );
751 else if( [o_save_accessory_popup indexOfSelectedItem] == 1 )
753 NSString * o_real_filename;
755 range.location = [o_filename length] - [@".xspf" length];
756 range.length = [@".xspf" length];
758 if( [o_filename compare:@".xspf" options: NSCaseInsensitiveSearch
759 range: range] != NSOrderedSame )
761 o_real_filename = [NSString stringWithFormat: @"%@.xspf", o_filename];
765 o_real_filename = o_filename;
767 playlist_Export( p_playlist,
768 [o_real_filename fileSystemRepresentation],
769 p_playlist->p_local_category, "export-xspf" );
773 NSString * o_real_filename;
775 range.location = [o_filename length] - [@".html" length];
776 range.length = [@".html" length];
778 if( [o_filename compare:@".html" options: NSCaseInsensitiveSearch
779 range: range] != NSOrderedSame )
781 o_real_filename = [NSString stringWithFormat: @"%@.html", o_filename];
785 o_real_filename = o_filename;
787 playlist_Export( p_playlist,
788 [o_real_filename fileSystemRepresentation],
789 p_playlist->p_local_category, "export-html" );
794 /* When called retrieves the selected outlineview row and plays that node or item */
795 - (IBAction)playItem:(id)sender
797 intf_thread_t * p_intf = VLCIntf;
798 playlist_t * p_playlist = pl_Get( p_intf );
800 playlist_item_t *p_item;
801 playlist_item_t *p_node = NULL;
803 p_item = [[o_outline_view itemAtRow:[o_outline_view selectedRow]] pointerValue];
808 if( p_item->i_children == -1 )
810 p_node = p_item->p_parent;
815 if( p_node->i_children > 0 && p_node->pp_children[0]->i_children == -1 )
817 p_item = p_node->pp_children[0];
824 playlist_Control( p_playlist, PLAYLIST_VIEWPLAY, pl_Locked, p_node, p_item );
829 - (IBAction)revealItemInFinder:(id)sender
831 playlist_item_t * p_item = [[o_outline_view itemAtRow:[o_outline_view selectedRow]] pointerValue];
832 NSMutableString * o_mrl = nil;
834 if(! p_item || !p_item->p_input )
837 char *psz_uri = decode_URI( input_item_GetURI( p_item->p_input ) );
839 o_mrl = [NSMutableString stringWithUTF8String: psz_uri];
841 /* perform some checks whether it is a file and if it is local at all... */
842 NSRange prefix_range = [o_mrl rangeOfString: @"file:"];
843 if( prefix_range.location != NSNotFound )
844 [o_mrl deleteCharactersInRange: prefix_range];
846 if( [o_mrl characterAtIndex:0] == '/' )
847 [[NSWorkspace sharedWorkspace] selectFile: o_mrl inFileViewerRootedAtPath: o_mrl];
850 /* When called retrieves the selected outlineview row and plays that node or item */
851 - (IBAction)preparseItem:(id)sender
854 NSIndexSet *o_selected_indexes;
855 intf_thread_t * p_intf = VLCIntf;
856 playlist_t * p_playlist = pl_Get( p_intf );
857 playlist_item_t *p_item = NULL;
859 o_selected_indexes = [o_outline_view selectedRowIndexes];
860 i_count = [o_selected_indexes count];
862 NSUInteger indexes[i_count];
863 [o_selected_indexes getIndexes:indexes maxCount:i_count inIndexRange:nil];
864 for (int i = 0; i < i_count; i++)
866 p_item = [[o_outline_view itemAtRow:indexes[i]] pointerValue];
867 [o_outline_view deselectRow: indexes[i]];
871 if( p_item->i_children == -1 )
872 playlist_PreparseEnqueue( p_playlist, p_item->p_input );
874 msg_Dbg( p_intf, "preparsing nodes not implemented" );
877 [self playlistUpdated];
880 - (IBAction)downloadCoverArt:(id)sender
883 NSIndexSet *o_selected_indexes;
884 intf_thread_t * p_intf = VLCIntf;
885 playlist_t * p_playlist = pl_Get( p_intf );
886 playlist_item_t *p_item = NULL;
888 o_selected_indexes = [o_outline_view selectedRowIndexes];
889 i_count = [o_selected_indexes count];
891 NSUInteger indexes[i_count];
892 [o_selected_indexes getIndexes:indexes maxCount:i_count inIndexRange:nil];
893 for (int i = 0; i < i_count; i++)
895 p_item = [[o_outline_view itemAtRow: indexes[i]] pointerValue];
896 [o_outline_view deselectRow: indexes[i]];
898 if( p_item && p_item->i_children == -1 )
899 playlist_AskForArtEnqueue( p_playlist, p_item->p_input );
901 [self playlistUpdated];
904 - (IBAction)servicesChange:(id)sender
906 NSMenuItem *o_mi = (NSMenuItem *)sender;
907 NSString *o_string = [o_mi representedObject];
908 playlist_t * p_playlist = pl_Get( VLCIntf );
909 if( !playlist_IsServicesDiscoveryLoaded( p_playlist, [o_string UTF8String] ) )
910 playlist_ServicesDiscoveryAdd( p_playlist, [o_string UTF8String] );
912 playlist_ServicesDiscoveryRemove( p_playlist, [o_string UTF8String] );
914 [o_mi setState: playlist_IsServicesDiscoveryLoaded( p_playlist,
915 [o_string UTF8String] ) ? YES : NO];
917 [self playlistUpdated];
921 - (IBAction)selectAll:(id)sender
923 [o_outline_view selectAll: nil];
926 - (IBAction)deleteItem:(id)sender
929 NSIndexSet *o_selected_indexes;
930 playlist_t * p_playlist;
931 intf_thread_t * p_intf = VLCIntf;
933 o_selected_indexes = [o_outline_view selectedRowIndexes];
934 i_count = [o_selected_indexes count];
936 p_playlist = pl_Get( p_intf );
938 NSUInteger indexes[i_count];
939 if (i_count == [o_outline_view numberOfRows])
942 msg_Dbg( p_intf, "user selected entire list, deleting current playlist root instead of individual items" );
945 playlist_NodeDelete( p_playlist, [self currentPlaylistRoot], true, false );
947 [self playlistUpdated];
950 [o_selected_indexes getIndexes:indexes maxCount:i_count inIndexRange:nil];
951 for (int i = 0; i < i_count; i++)
953 id o_item = [o_outline_view itemAtRow: indexes[i]];
954 [o_outline_view deselectRow: indexes[i]];
957 playlist_item_t *p_item = [o_item pointerValue];
959 msg_Dbg( p_intf, "deleting item %i (of %i) with id \"%i\", pointerValue \"%p\" and %i children", i+1, i_count,
960 p_item->p_input->i_id, [o_item pointerValue], p_item->i_children +1 );
963 if( p_item->i_children != -1 )
964 //is a node and not an item
966 if( playlist_Status( p_playlist ) != PLAYLIST_STOPPED &&
967 [self isItem: playlist_CurrentPlayingItem( p_playlist ) inNode: ((playlist_item_t *)[o_item pointerValue])
968 checkItemExistence: NO locked:YES] == YES )
969 // if current item is in selected node and is playing then stop playlist
970 playlist_Control(p_playlist, PLAYLIST_STOP, pl_Locked );
972 playlist_NodeDelete( p_playlist, p_item, true, false );
975 playlist_DeleteFromInput( p_playlist, p_item->p_input, pl_Locked );
978 [o_outline_dict removeObjectForKey:[NSString stringWithFormat:@"%p", [o_item pointerValue]]];
982 [self playlistUpdated];
985 - (IBAction)sortNodeByName:(id)sender
987 [self sortNode: SORT_TITLE];
990 - (IBAction)sortNodeByAuthor:(id)sender
992 [self sortNode: SORT_ARTIST];
995 - (void)sortNode:(int)i_mode
997 playlist_t * p_playlist = pl_Get( VLCIntf );
998 playlist_item_t * p_item;
1000 if( [o_outline_view selectedRow] > -1 )
1002 p_item = [[o_outline_view itemAtRow: [o_outline_view selectedRow]] pointerValue];
1005 /*If no item is selected, sort the whole playlist*/
1007 p_item = [self currentPlaylistRoot];
1011 if( p_item->i_children > -1 ) // the item is a node
1013 playlist_RecursiveNodeSort( p_playlist, p_item, i_mode, ORDER_NORMAL );
1017 playlist_RecursiveNodeSort( p_playlist,
1018 p_item->p_parent, i_mode, ORDER_NORMAL );
1021 [self playlistUpdated];
1024 - (input_item_t *)createItem:(NSDictionary *)o_one_item
1026 intf_thread_t * p_intf = VLCIntf;
1027 playlist_t * p_playlist = pl_Get( p_intf );
1029 input_item_t *p_input;
1030 BOOL b_rem = FALSE, b_dir = FALSE;
1031 NSString *o_uri, *o_name;
1036 o_uri = (NSString *)[o_one_item objectForKey: @"ITEM_URL"];
1037 o_name = (NSString *)[o_one_item objectForKey: @"ITEM_NAME"];
1038 o_options = (NSArray *)[o_one_item objectForKey: @"ITEM_OPTIONS"];
1040 /* Find the name for a disc entry (i know, can you believe the trouble?) */
1041 if( ( !o_name || [o_name isEqualToString:@""] ) && [o_uri rangeOfString: @"/dev/"].location != NSNotFound )
1044 struct statfs *mounts = NULL;
1046 i_count = getmntinfo (&mounts, MNT_NOWAIT);
1047 /* getmntinfo returns a pointer to static data. Do not free. */
1048 for( int i_index = 0 ; i_index < i_count; i_index++ )
1050 NSMutableString *o_temp, *o_temp2;
1051 o_temp = [NSMutableString stringWithString: o_uri];
1052 o_temp2 = [NSMutableString stringWithUTF8String: mounts[i_index].f_mntfromname];
1053 [o_temp replaceOccurrencesOfString: @"/dev/rdisk" withString: @"/dev/disk" options:NSLiteralSearch range:NSMakeRange(0, [o_temp length]) ];
1054 [o_temp2 replaceOccurrencesOfString: @"s0" withString: @"" options:NSLiteralSearch range:NSMakeRange(0, [o_temp2 length]) ];
1055 [o_temp2 replaceOccurrencesOfString: @"s1" withString: @"" options:NSLiteralSearch range:NSMakeRange(0, [o_temp2 length]) ];
1057 if( strstr( [o_temp fileSystemRepresentation], [o_temp2 fileSystemRepresentation] ) != NULL )
1059 o_name = [[NSFileManager defaultManager] displayNameAtPath: [NSString stringWithUTF8String:mounts[i_index].f_mntonname]];
1064 if( [[NSFileManager defaultManager] fileExistsAtPath:o_uri isDirectory:&b_dir] && b_dir &&
1065 [[NSWorkspace sharedWorkspace] getFileSystemInfoForPath: o_uri isRemovable: &b_rem
1066 isWritable:NULL isUnmountable:NULL description:NULL type:NULL] && b_rem )
1068 /* All of this is to make sure CD's play when you D&D them on VLC */
1069 /* Converts mountpoint to a /dev file */
1072 NSMutableString *o_temp;
1074 buf = (struct statfs *) malloc (sizeof(struct statfs));
1075 statfs( [o_uri fileSystemRepresentation], buf );
1076 psz_dev = strdup(buf->f_mntfromname);
1077 o_temp = [NSMutableString stringWithUTF8String: psz_dev ];
1078 [o_temp replaceOccurrencesOfString: @"/dev/disk" withString: @"/dev/rdisk" options:NSLiteralSearch range:NSMakeRange(0, [o_temp length]) ];
1079 [o_temp replaceOccurrencesOfString: @"s0" withString: @"" options:NSLiteralSearch range:NSMakeRange(0, [o_temp length]) ];
1080 [o_temp replaceOccurrencesOfString: @"s1" withString: @"" options:NSLiteralSearch range:NSMakeRange(0, [o_temp length]) ];
1084 p_input = input_item_New( [o_uri fileSystemRepresentation], o_name ? [o_name UTF8String] : NULL );
1090 NSUInteger count = [o_options count];
1091 for( NSUInteger i = 0; i < count; i++ )
1093 input_item_AddOption( p_input, [[o_options objectAtIndex:i] UTF8String],
1094 VLC_INPUT_OPTION_TRUSTED );
1098 /* Recent documents menu */
1099 o_true_file = [NSURL URLWithString: o_uri];
1100 if( o_true_file != nil && (BOOL)config_GetInt( p_playlist, "macosx-recentitems" ) == YES )
1102 [[NSDocumentController sharedDocumentController]
1103 noteNewRecentDocumentURL: o_true_file];
1108 - (void)appendArray:(NSArray*)o_array atPos:(int)i_position enqueue:(BOOL)b_enqueue
1110 playlist_t * p_playlist = pl_Get( VLCIntf );
1111 NSUInteger count = [o_array count];
1112 BOOL b_usingPlaylist;
1113 if ([self currentPlaylistRoot] == p_playlist->p_ml_category)
1114 b_usingPlaylist = NO;
1116 b_usingPlaylist = YES;
1119 for( NSUInteger i_item = 0; i_item < count; i_item++ )
1121 input_item_t *p_input;
1122 NSDictionary *o_one_item;
1125 o_one_item = [o_array objectAtIndex: i_item];
1126 p_input = [self createItem: o_one_item];
1133 /* FIXME: playlist_AddInput() can fail */
1135 playlist_AddInput( p_playlist, p_input, PLAYLIST_INSERT, i_position == -1 ? PLAYLIST_END : i_position + i_item, b_usingPlaylist,
1138 if( i_item == 0 && !b_enqueue )
1140 playlist_item_t *p_item = playlist_ItemGetByInput( p_playlist, p_input );
1141 playlist_Control( p_playlist, PLAYLIST_VIEWPLAY, pl_Locked, p_item->p_parent, p_item );
1144 vlc_gc_decref( p_input );
1147 [self playlistUpdated];
1150 - (void)appendNodeArray:(NSArray*)o_array inNode:(playlist_item_t *)p_node atPos:(int)i_position enqueue:(BOOL)b_enqueue
1152 playlist_t * p_playlist = pl_Get( VLCIntf );
1153 NSUInteger count = [o_array count];
1155 for( NSUInteger i_item = 0; i_item < count; i_item++ )
1157 input_item_t *p_input;
1158 NSDictionary *o_one_item;
1161 o_one_item = [o_array objectAtIndex: i_item];
1162 p_input = [self createItem: o_one_item];
1164 if( !p_input ) continue;
1168 playlist_NodeAddInput( p_playlist, p_input, p_node,
1171 PLAYLIST_END : i_position + i_item,
1175 if( i_item == 0 && !b_enqueue )
1177 playlist_item_t *p_item;
1178 p_item = playlist_ItemGetByInput( p_playlist, p_input );
1179 playlist_Control( p_playlist, PLAYLIST_VIEWPLAY, pl_Locked, p_node, p_item );
1182 vlc_gc_decref( p_input );
1184 [self playlistUpdated];
1187 - (NSMutableArray *)subSearchItem:(playlist_item_t *)p_item
1189 playlist_t *p_playlist = pl_Get( VLCIntf );
1190 playlist_item_t *p_selected_item;
1193 i_selected_row = [o_outline_view selectedRow];
1194 if (i_selected_row < 0)
1197 p_selected_item = (playlist_item_t *)[[o_outline_view itemAtRow: i_selected_row] pointerValue];
1199 for( NSUInteger i_current = 0; i_current < p_item->i_children ; i_current++ )
1202 NSString *o_current_name, *o_current_author;
1205 o_current_name = [NSString stringWithUTF8String:
1206 p_item->pp_children[i_current]->p_input->psz_name];
1207 psz_temp = input_item_GetInfo( p_item->p_input ,
1208 _("Meta-information"),_("Artist") );
1209 o_current_author = [NSString stringWithUTF8String: psz_temp];
1213 if( p_selected_item == p_item->pp_children[i_current] &&
1214 b_selected_item_met == NO )
1216 b_selected_item_met = YES;
1218 else if( p_selected_item == p_item->pp_children[i_current] &&
1219 b_selected_item_met == YES )
1223 else if( b_selected_item_met == YES &&
1224 ( [o_current_name rangeOfString:[o_search_field
1225 stringValue] options:NSCaseInsensitiveSearch].length ||
1226 [o_current_author rangeOfString:[o_search_field
1227 stringValue] options:NSCaseInsensitiveSearch].length ) )
1229 /*Adds the parent items in the result array as well, so that we can
1231 return [NSMutableArray arrayWithObject: [NSValue
1232 valueWithPointer: p_item->pp_children[i_current]]];
1234 if( p_item->pp_children[i_current]->i_children > 0 )
1236 id o_result = [self subSearchItem:
1237 p_item->pp_children[i_current]];
1238 if( o_result != NULL )
1240 [o_result insertObject: [NSValue valueWithPointer:
1241 p_item->pp_children[i_current]] atIndex:0];
1249 - (IBAction)searchItem:(id)sender
1251 playlist_t * p_playlist = pl_Get( VLCIntf );
1256 b_selected_item_met = NO;
1258 /*First, only search after the selected item:*
1259 *(b_selected_item_met = NO) */
1260 o_result = [self subSearchItem:[self currentPlaylistRoot]];
1261 if( o_result == NULL )
1263 /* If the first search failed, search again from the beginning */
1264 o_result = [self subSearchItem:[self currentPlaylistRoot]];
1266 if( o_result != NULL )
1269 if( [[o_result objectAtIndex: 0] pointerValue] == p_playlist->p_local_category )
1273 NSUInteger count = [o_result count];
1275 for( NSUInteger i = i_start ; i < count - 1 ; i++ )
1277 [o_outline_view expandItem: [o_outline_dict objectForKey:
1278 [NSString stringWithFormat: @"%p",
1279 [[o_result objectAtIndex: i] pointerValue]]]];
1281 i_row = [o_outline_view rowForItem: [o_outline_dict objectForKey:
1282 [NSString stringWithFormat: @"%p",
1283 [[o_result objectAtIndex: count - 1 ]
1288 [o_outline_view selectRowIndexes:[NSIndexSet indexSetWithIndex:i_row] byExtendingSelection:NO];
1289 [o_outline_view scrollRowToVisible: i_row];
1293 - (IBAction)recursiveExpandNode:(id)sender
1295 id o_item = [o_outline_view itemAtRow: [o_outline_view selectedRow]];
1296 playlist_item_t *p_item = (playlist_item_t *)[o_item pointerValue];
1298 if( ![[o_outline_view dataSource] outlineView: o_outline_view
1299 isItemExpandable: o_item] )
1301 o_item = [o_outline_dict objectForKey: [NSString stringWithFormat: @"%p", p_item->p_parent]];
1304 /* We need to collapse the node first, since OSX refuses to recursively
1305 expand an already expanded node, even if children nodes are collapsed. */
1306 [o_outline_view collapseItem: o_item collapseChildren: YES];
1307 [o_outline_view expandItem: o_item expandChildren: YES];
1310 - (NSMenu *)menuForEvent:(NSEvent *)o_event
1316 pt = [o_outline_view convertPoint: [o_event locationInWindow]
1318 int row = [o_outline_view rowAtPoint:pt];
1320 [o_outline_view selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO];
1322 b_item_sel = ( row != -1 && [o_outline_view selectedRow] != -1 );
1323 b_rows = [o_outline_view numberOfRows] != 0;
1325 [o_mi_play setEnabled: b_item_sel];
1326 [o_mi_delete setEnabled: b_item_sel];
1327 [o_mi_selectall setEnabled: b_rows];
1328 [o_mi_info setEnabled: b_item_sel];
1329 [o_mi_preparse setEnabled: b_item_sel];
1330 [o_mi_recursive_expand setEnabled: b_item_sel];
1331 [o_mi_sort_name setEnabled: b_item_sel];
1332 [o_mi_sort_author setEnabled: b_item_sel];
1334 return( o_ctx_menu );
1337 - (void)outlineView: (NSOutlineView *)o_tv
1338 didClickTableColumn:(NSTableColumn *)o_tc
1340 int i_mode, i_type = 0;
1341 intf_thread_t *p_intf = VLCIntf;
1343 playlist_t *p_playlist = pl_Get( p_intf );
1345 /* Check whether the selected table column header corresponds to a
1346 sortable table column*/
1347 if( !( o_tc == o_tc_name || o_tc == o_tc_author ) )
1352 if( o_tc_sortColumn == o_tc )
1354 b_isSortDescending = !b_isSortDescending;
1358 b_isSortDescending = false;
1361 if( o_tc == o_tc_name )
1363 i_mode = SORT_TITLE;
1365 else if( o_tc == o_tc_author )
1367 i_mode = SORT_ARTIST;
1370 if( b_isSortDescending )
1372 i_type = ORDER_REVERSE;
1376 i_type = ORDER_NORMAL;
1380 playlist_RecursiveNodeSort( p_playlist, [self currentPlaylistRoot], i_mode, i_type );
1383 [self playlistUpdated];
1385 o_tc_sortColumn = o_tc;
1386 [o_outline_view setHighlightedTableColumn:o_tc];
1388 if( b_isSortDescending )
1390 [o_outline_view setIndicatorImage:o_descendingSortingImage
1391 inTableColumn:o_tc];
1395 [o_outline_view setIndicatorImage:o_ascendingSortingImage
1396 inTableColumn:o_tc];
1401 - (void)outlineView:(NSOutlineView *)outlineView
1402 willDisplayCell:(id)cell
1403 forTableColumn:(NSTableColumn *)tableColumn
1406 /* this method can be called when VLC is already dead, hence the extra checks */
1407 intf_thread_t * p_intf = VLCIntf;
1410 playlist_t *p_playlist = pl_Get( p_intf );
1417 o_playing_item = [o_outline_dict objectForKey: [NSString stringWithFormat:@"%p", playlist_CurrentPlayingItem( p_playlist )]];
1420 if( [self isItem: [o_playing_item pointerValue] inNode:
1421 [item pointerValue] checkItemExistence: YES]
1422 || [o_playing_item isEqual: item] )
1424 [cell setFont: [[NSFontManager sharedFontManager] convertFont:[cell font] toHaveTrait:NSBoldFontMask]];
1428 [cell setFont: [[NSFontManager sharedFontManager] convertFont:[cell font] toNotHaveTrait:NSBoldFontMask]];
1434 playlist_t *p_playlist = pl_Get( VLCIntf );
1439 o_playing_item = [o_outline_dict objectForKey: [NSString stringWithFormat:@"%p", playlist_CurrentPlayingItem( p_playlist )]];
1442 return o_playing_item;
1446 @implementation VLCPlaylist (NSOutlineViewDataSource)
1448 - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item
1450 id o_value = [super outlineView: outlineView child: index ofItem: item];
1452 [o_outline_dict setObject:o_value forKey:[NSString stringWithFormat:@"%p", [o_value pointerValue]]];
1456 /* Required for drag & drop and reordering */
1457 - (BOOL)outlineView:(NSOutlineView *)outlineView writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pboard
1459 playlist_t *p_playlist = pl_Get( VLCIntf );
1461 /* First remove the items that were moved during the last drag & drop
1463 [o_items_array removeAllObjects];
1464 [o_nodes_array removeAllObjects];
1466 NSUInteger itemCount = [items count];
1468 for( NSUInteger i = 0 ; i < itemCount ; i++ )
1470 id o_item = [items objectAtIndex: i];
1472 /* Refuse to move items that are not in the General Node
1473 (Service Discovery) */
1474 if( (![self isItem: [o_item pointerValue] inNode: p_playlist->p_local_category checkItemExistence: NO] &&
1475 var_CreateGetBool( p_playlist, "media-library" ) && ![self isItem: [o_item pointerValue] inNode: p_playlist->p_ml_category checkItemExistence: NO]) ||
1476 [o_item pointerValue] == p_playlist->p_local_category ||
1477 [o_item pointerValue] == p_playlist->p_ml_category )
1481 /* Fill the items and nodes to move in 2 different arrays */
1482 if( ((playlist_item_t *)[o_item pointerValue])->i_children > 0 )
1483 [o_nodes_array addObject: o_item];
1485 [o_items_array addObject: o_item];
1488 /* Now we need to check if there are selected items that are in already
1489 selected nodes. In that case, we only want to move the nodes */
1490 [self removeItemsFrom: o_nodes_array ifChildrenOf: o_nodes_array];
1491 [self removeItemsFrom: o_items_array ifChildrenOf: o_nodes_array];
1493 /* We add the "VLCPlaylistItemPboardType" type to be able to recognize
1494 a Drop operation coming from the playlist. */
1496 [pboard declareTypes: [NSArray arrayWithObjects:
1497 @"VLCPlaylistItemPboardType", nil] owner: self];
1498 [pboard setData:[NSData data] forType:@"VLCPlaylistItemPboardType"];
1503 - (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id <NSDraggingInfo>)info proposedItem:(id)item proposedChildIndex:(NSInteger)index
1505 playlist_t *p_playlist = pl_Get( VLCIntf );
1506 NSPasteboard *o_pasteboard = [info draggingPasteboard];
1508 if( !p_playlist ) return NSDragOperationNone;
1510 /* Dropping ON items is not allowed if item is not a node */
1513 if( index == NSOutlineViewDropOnItemIndex &&
1514 ((playlist_item_t *)[item pointerValue])->i_children == -1 )
1516 return NSDragOperationNone;
1520 /* We refuse to drop an item in anything else than a child of the General
1521 Node. We still accept items that would be root nodes of the outlineview
1522 however, to allow drop in an empty playlist. */
1523 if( !( ([self isItem: [item pointerValue] inNode: p_playlist->p_local_category checkItemExistence: NO] ||
1524 ( var_CreateGetBool( p_playlist, "media-library" ) && [self isItem: [item pointerValue] inNode: p_playlist->p_ml_category checkItemExistence: NO] ) ) || item == nil ) )
1526 return NSDragOperationNone;
1529 /* Drop from the Playlist */
1530 if( [[o_pasteboard types] containsObject: @"VLCPlaylistItemPboardType"] )
1532 NSUInteger count = [o_nodes_array count];
1533 for( NSUInteger i = 0 ; i < count ; i++ )
1535 /* We refuse to Drop in a child of an item we are moving */
1536 if( [self isItem: [item pointerValue] inNode:
1537 [[o_nodes_array objectAtIndex: i] pointerValue]
1538 checkItemExistence: NO] )
1540 return NSDragOperationNone;
1543 return NSDragOperationMove;
1546 /* Drop from the Finder */
1547 else if( [[o_pasteboard types] containsObject: NSFilenamesPboardType] )
1549 return NSDragOperationGeneric;
1551 return NSDragOperationNone;
1554 - (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id <NSDraggingInfo>)info item:(id)item childIndex:(NSInteger)index
1556 playlist_t * p_playlist = pl_Get( VLCIntf );
1557 NSPasteboard *o_pasteboard = [info draggingPasteboard];
1559 /* Drag & Drop inside the playlist */
1560 if( [[o_pasteboard types] containsObject: @"VLCPlaylistItemPboardType"] )
1562 int i_row, i_removed_from_node = 0;
1563 playlist_item_t *p_new_parent, *p_item = NULL;
1564 NSArray *o_all_items = [o_nodes_array arrayByAddingObjectsFromArray:
1566 /* If the item is to be dropped as root item of the outline, make it a
1567 child of the respective general node, if is either the pl or the ml
1568 Else, choose the proposed parent as parent. */
1571 if ([self currentPlaylistRoot] == p_playlist->p_local_category || [self currentPlaylistRoot] == p_playlist->p_ml_category)
1572 p_new_parent = [self currentPlaylistRoot];
1574 p_new_parent = p_playlist->p_local_category;
1577 p_new_parent = [item pointerValue];
1579 /* Make sure the proposed parent is a node.
1580 (This should never be true) */
1581 if( p_new_parent->i_children < 0 )
1586 NSUInteger count = [o_all_items count];
1587 for( NSUInteger i = 0; i < count; i++ )
1589 playlist_item_t *p_old_parent = NULL;
1590 int i_old_index = 0;
1592 p_item = [[o_all_items objectAtIndex:i] pointerValue];
1593 p_old_parent = p_item->p_parent;
1596 /* We may need the old index later */
1597 if( p_new_parent == p_old_parent )
1599 for( NSInteger j = 0; j < p_old_parent->i_children; j++ )
1601 if( p_old_parent->pp_children[j] == p_item )
1610 // Actually detach the item from the old position
1611 if( playlist_NodeRemoveItem( p_playlist, p_item, p_old_parent ) ==
1615 /* Calculate the new index */
1618 /* If we move the item in the same node, we need to take into
1619 account that one item will be deleted */
1622 if ((p_new_parent == p_old_parent && i_old_index < index + (int)i) )
1624 i_removed_from_node++;
1626 i_new_index = index + i - i_removed_from_node;
1628 // Reattach the item to the new position
1629 playlist_NodeInsert( p_playlist, p_item, p_new_parent, i_new_index );
1633 [self playlistUpdated];
1634 i_row = [o_outline_view rowForItem:[o_outline_dict objectForKey:[NSString stringWithFormat: @"%p", [[o_all_items objectAtIndex: 0] pointerValue]]]];
1638 i_row = [o_outline_view rowForItem:[o_outline_dict objectForKey:[NSString stringWithFormat: @"%p", p_new_parent]]];
1641 [o_outline_view deselectAll: self];
1642 [o_outline_view selectRowIndexes:[NSIndexSet indexSetWithIndex:i_row] byExtendingSelection:NO];
1643 [o_outline_view scrollRowToVisible: i_row];
1648 else if( [[o_pasteboard types] containsObject: NSFilenamesPboardType] )
1650 playlist_item_t *p_node = [item pointerValue];
1652 NSArray *o_values = [[o_pasteboard propertyListForType: NSFilenamesPboardType]
1653 sortedArrayUsingSelector: @selector(caseInsensitiveCompare:)];
1654 NSUInteger count = [o_values count];
1655 NSMutableArray *o_array = [NSMutableArray arrayWithCapacity:count];
1656 input_thread_t * p_input = pl_CurrentInput( VLCIntf );
1657 BOOL b_returned = NO;
1659 if (count == 1 && p_input)
1661 b_returned = input_AddSubtitle( p_input, make_URI([[o_values objectAtIndex:0] UTF8String], NULL), true );
1662 vlc_object_release( p_input );
1667 vlc_object_release( p_input );
1669 for( NSUInteger i = 0; i < count; i++)
1671 NSDictionary *o_dic;
1672 char *psz_uri = make_URI([[o_values objectAtIndex:i] UTF8String], NULL);
1676 o_dic = [NSDictionary dictionaryWithObject:[NSString stringWithCString:psz_uri encoding:NSUTF8StringEncoding] forKey:@"ITEM_URL"];
1680 [o_array addObject: o_dic];
1685 [self appendArray:o_array atPos:index enqueue: YES];
1689 assert( p_node->i_children != -1 );
1690 [self appendNodeArray:o_array inNode: p_node atPos:index enqueue:YES];