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
33 /*****************************************************************************
35 *****************************************************************************/
36 #include <stdlib.h> /* malloc(), free() */
37 #include <sys/param.h> /* for MAXPATHLEN */
40 #include <sys/mount.h>
45 #import "playlistinfo.h"
54 #import <vlc_interface.h>
58 /*****************************************************************************
59 * VLCPlaylistView implementation
60 *****************************************************************************/
61 @implementation VLCPlaylistView
63 - (NSMenu *)menuForEvent:(NSEvent *)o_event
65 return( [(VLCPlaylist *)[self delegate] menuForEvent: o_event] );
68 - (void)keyDown:(NSEvent *)o_event
72 if( [[o_event characters] length] )
74 key = [[o_event characters] characterAtIndex: 0];
79 case NSDeleteCharacter:
80 case NSDeleteFunctionKey:
81 case NSDeleteCharFunctionKey:
82 case NSBackspaceCharacter:
83 [(VLCPlaylist *)[self delegate] deleteItem:self];
86 case NSEnterCharacter:
87 case NSCarriageReturnCharacter:
88 [(VLCPlaylist *)[[VLCMain sharedInstance] playlist] playItem:nil];
92 [super keyDown: o_event];
97 - (BOOL)validateMenuItem:(NSMenuItem *)item
99 if (([self numberOfSelectedRows] >= 1 && [item action] == @selector(delete:)) || [item action] == @selector(selectAll:))
105 - (BOOL) acceptsFirstResponder
110 - (BOOL) becomeFirstResponder
112 [self setNeedsDisplay:YES];
116 - (BOOL) resignFirstResponder
118 [self setNeedsDisplay:YES];
122 - (IBAction)delete:(id)sender
124 [[[VLCMain sharedInstance] playlist] deleteItem: sender];
129 /*****************************************************************************
130 * VLCPlaylistCommon implementation
132 * This class the superclass of the VLCPlaylist and VLCPlaylistWizard.
133 * It contains the common methods and elements of these 2 entities.
134 *****************************************************************************/
135 @implementation VLCPlaylistCommon
139 playlist_t * p_playlist = pl_Get( VLCIntf );
140 p_current_root_item = p_playlist->p_local_category;
145 o_outline_dict = [[NSMutableDictionary alloc] init];
152 playlist_t * p_playlist = pl_Get( VLCIntf );
153 [o_outline_view setTarget: self];
154 [o_outline_view setDelegate: self];
155 [o_outline_view setDataSource: self];
156 [o_outline_view setAllowsEmptySelection: NO];
157 [o_outline_view expandItem: [o_outline_view itemAtRow:0]];
159 [o_outline_view_other setTarget: self];
160 [o_outline_view_other setDelegate: self];
161 [o_outline_view_other setDataSource: self];
162 [o_outline_view_other setAllowsEmptySelection: NO];
164 [[o_tc_name_other headerCell] setStringValue:_NS("Name")];
165 [[o_tc_author_other headerCell] setStringValue:_NS("Author")];
166 [[o_tc_duration_other headerCell] setStringValue:_NS("Duration")];
168 [self setColumn: TITLE_COLUMN state: NSOnState];
169 [self setColumn: ARTIST_COLUMN state: NSOnState];
170 [self setColumn: DURATION_COLUMN state: NSOnState];
171 id o_menu = [[VLCMain sharedInstance] mainMenu];
172 [o_menu setPlaylistColumnTableState: NSOnState forColumn: TITLE_COLUMN];
173 [o_menu setPlaylistColumnTableState: NSOnState forColumn: ARTIST_COLUMN];
174 [o_menu setPlaylistColumnTableState: NSOnState forColumn: DURATION_COLUMN];
177 - (void)setPlaylistRoot: (playlist_item_t *)root_item
179 p_current_root_item = root_item;
180 [o_outline_view reloadData];
181 [o_outline_view_other reloadData];
184 - (playlist_item_t *)currentPlaylistRoot
186 return p_current_root_item;
189 - (NSOutlineView *)outlineView
191 return o_outline_view;
194 - (playlist_item_t *)selectedPlaylistItem
196 return [[o_outline_view itemAtRow: [o_outline_view selectedRow]]
200 - (void)setColumn: (NSString *)o_column state: (NSInteger)i_state
202 NSTableColumn * o_work_tc;
204 if (i_state == NSOnState)
206 o_work_tc = [[NSTableColumn alloc] initWithIdentifier: o_column];
207 [o_work_tc setEditable: NO];
208 [[o_work_tc dataCell] setFont: [NSFont controlContentFontOfSize:11.]];
210 /* we cannot use a makro here, because gettext isn't clever enough for that */
211 if ([o_column isEqualToString: TRACKNUM_COLUMN])
213 [o_work_tc setMaxWidth: 20.];
214 [[o_work_tc headerCell] setStringValue: @"#"];
216 else if ([o_column isEqualToString: TITLE_COLUMN])
217 [[o_work_tc headerCell] setStringValue: _NS("Name")];
218 else if ([o_column isEqualToString: ARTIST_COLUMN])
219 [[o_work_tc headerCell] setStringValue: _NS("Author")];
220 else if ([o_column isEqualToString: DURATION_COLUMN])
221 [[o_work_tc headerCell] setStringValue: _NS("Duration")];
222 else if ([o_column isEqualToString: GENRE_COLUMN])
223 [[o_work_tc headerCell] setStringValue: _NS("Genre")];
224 else if ([o_column isEqualToString: ALBUM_COLUMN])
225 [[o_work_tc headerCell] setStringValue: _NS("Album")];
226 else if ([o_column isEqualToString: DESCRIPTION_COLUMN])
227 [[o_work_tc headerCell] setStringValue: _NS("Description")];
228 else if ([o_column isEqualToString: DATE_COLUMN])
229 [[o_work_tc headerCell] setStringValue: _NS("Date")];
230 else if ([o_column isEqualToString: LANGUAGE_COLUMN])
231 [[o_work_tc headerCell] setStringValue: _NS("Language")];
233 [o_outline_view addTableColumn: o_work_tc];
235 [o_outline_view reloadData];
236 [o_outline_view setNeedsDisplay: YES];
239 [o_outline_view removeTableColumn: [o_outline_view tableColumnWithIdentifier: o_column]];
244 @implementation VLCPlaylistCommon (NSOutlineViewDataSource)
245 /* return the number of children for Obj-C pointer item */ /* DONE */
246 - (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
249 playlist_item_t *p_item = NULL;
250 playlist_t * p_playlist = pl_Get( VLCIntf );
251 //assert( outlineView == o_outline_view );
256 p_item = p_current_root_item;
259 p_item = (playlist_item_t *)[item pointerValue];
262 i_return = p_item->i_children;
265 return i_return > 0 ? i_return : 0;
268 /* return the child at index for the Obj-C pointer item */ /* DONE */
269 - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item
271 playlist_item_t *p_return = NULL, *p_item = NULL;
273 playlist_t * p_playlist = pl_Get( VLCIntf );
279 p_item = p_current_root_item;
283 p_item = (playlist_item_t *)[item pointerValue];
285 if( p_item && index < p_item->i_children && index >= 0 )
286 p_return = p_item->pp_children[index];
289 o_value = [o_outline_dict objectForKey:[NSString stringWithFormat: @"%p", p_return]];
293 /* FIXME: Why is there a warning if that happens all the time and seems
294 * to be normal? Add an assert and fix it.
295 * msg_Warn( VLCIntf, "playlist item misses pointer value, adding one" ); */
296 o_value = [[NSValue valueWithPointer: p_return] retain];
301 /* is the item expandable */
302 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
305 playlist_t *p_playlist = pl_Get( VLCIntf );
311 if( p_current_root_item )
313 i_return = p_current_root_item->i_children;
318 playlist_item_t *p_item = (playlist_item_t *)[item pointerValue];
320 i_return = p_item->i_children;
324 return (i_return >= 0);
327 /* retrieve the string values for the cells */
328 - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)o_tc byItem:(id)item
332 playlist_item_t *p_item;
334 /* For error handling */
335 static BOOL attempted_reload = NO;
337 if( item == nil || ![item isKindOfClass: [NSValue class]] )
339 /* Attempt to fix the error by asking for a data redisplay
340 * This might cause infinite loop, so add a small check */
341 if( !attempted_reload )
343 attempted_reload = YES;
344 [outlineView reloadData];
349 p_item = (playlist_item_t *)[item pointerValue];
350 if( !p_item || !p_item->p_input )
352 /* Attempt to fix the error by asking for a data redisplay
353 * This might cause infinite loop, so add a small check */
354 if( !attempted_reload )
356 attempted_reload = YES;
357 [outlineView reloadData];
362 attempted_reload = NO;
363 NSString * o_identifier = [o_tc identifier];
365 if( [o_identifier isEqualToString:TRACKNUM_COLUMN] )
367 psz_value = input_item_GetTrackNumber( p_item->p_input ); \
369 o_value = [NSString stringWithUTF8String: psz_value]; \
373 else if( [o_identifier isEqualToString:TITLE_COLUMN] )
375 /* sanity check to prevent the NSString class from crashing */
376 char *psz_title = input_item_GetTitleFbName( p_item->p_input );
379 o_value = [NSString stringWithUTF8String: psz_title];
383 else if( [o_identifier isEqualToString:ARTIST_COLUMN] )
385 psz_value = input_item_GetArtist( p_item->p_input ); \
387 o_value = [NSString stringWithUTF8String: psz_value]; \
391 else if( [o_identifier isEqualToString:@"duration"] )
393 char psz_duration[MSTRTIME_MAX_SIZE];
394 mtime_t dur = input_item_GetDuration( p_item->p_input );
397 secstotimestr( psz_duration, dur/1000000 );
398 o_value = [NSString stringWithUTF8String: psz_duration];
403 else if( [o_identifier isEqualToString:GENRE_COLUMN] )
405 psz_value = input_item_GetGenre( p_item->p_input ); \
407 o_value = [NSString stringWithUTF8String: psz_value]; \
411 else if( [o_identifier isEqualToString:ALBUM_COLUMN] )
413 psz_value = input_item_GetAlbum( p_item->p_input ); \
415 o_value = [NSString stringWithUTF8String: psz_value]; \
419 else if( [o_identifier isEqualToString:DESCRIPTION_COLUMN] )
421 psz_value = input_item_GetDescription( p_item->p_input ); \
423 o_value = [NSString stringWithUTF8String: psz_value]; \
427 else if( [o_identifier isEqualToString:DATE_COLUMN] )
429 psz_value = input_item_GetDate( p_item->p_input ); \
431 o_value = [NSString stringWithUTF8String: psz_value]; \
435 else if( [o_identifier isEqualToString:LANGUAGE_COLUMN] )
437 psz_value = input_item_GetLanguage( p_item->p_input ); \
439 o_value = [NSString stringWithUTF8String: psz_value]; \
443 else if( [o_identifier isEqualToString:@"status"] )
445 if( input_item_HasErrorWhenReading( p_item->p_input ) )
447 o_value = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kAlertCautionIcon)];
448 [o_value setSize: NSMakeSize(16,16)];
457 /*****************************************************************************
458 * VLCPlaylistWizard implementation
459 *****************************************************************************/
460 @implementation VLCPlaylistWizard
462 - (IBAction)reloadOutlineView
464 /* Only reload the outlineview if the wizard window is open since this can
465 be quite long on big playlists */
466 if( [[o_outline_view window] isVisible] )
468 [o_outline_view reloadData];
474 /*****************************************************************************
475 * extension to NSOutlineView's interface to fix compilation warnings
476 * and let us access these 2 functions properly
477 * this uses a private Apple-API, but works fine on all current OSX releases
478 * keep checking for compatiblity with future releases though
479 *****************************************************************************/
481 @interface NSOutlineView (UndocumentedSortImages)
482 + (NSImage *)_defaultTableHeaderSortImage;
483 + (NSImage *)_defaultTableHeaderReverseSortImage;
487 /*****************************************************************************
488 * VLCPlaylist implementation
489 *****************************************************************************/
490 @implementation VLCPlaylist
497 o_nodes_array = [[NSMutableArray alloc] init];
498 o_items_array = [[NSMutableArray alloc] init];
505 [o_nodes_array release];
506 [o_items_array release];
512 playlist_t * p_playlist = pl_Get( VLCIntf );
516 [super awakeFromNib];
518 [o_outline_view setDoubleAction: @selector(playItem:)];
519 [o_outline_view_other setDoubleAction: @selector(playItem:)];
521 [o_outline_view registerForDraggedTypes:
522 [NSArray arrayWithObjects: NSFilenamesPboardType,
523 @"VLCPlaylistItemPboardType", nil]];
524 [o_outline_view setIntercellSpacing: NSMakeSize (0.0, 1.0)];
526 [o_outline_view_other registerForDraggedTypes:
527 [NSArray arrayWithObjects: NSFilenamesPboardType,
528 @"VLCPlaylistItemPboardType", nil]];
529 [o_outline_view_other setIntercellSpacing: NSMakeSize (0.0, 1.0)];
531 /* This uses private Apple API which works fine until 10.5.
532 * We need to keep checking in the future!
533 * These methods are being added artificially to NSOutlineView's interface above */
534 o_ascendingSortingImage = [[NSOutlineView class] _defaultTableHeaderSortImage];
535 o_descendingSortingImage = [[NSOutlineView class] _defaultTableHeaderReverseSortImage];
537 o_tc_sortColumn = nil;
540 - (void)searchfieldChanged:(NSNotification *)o_notification
542 [o_search_field setStringValue:[[o_notification object] stringValue]];
547 [o_mi_save_playlist setTitle: _NS("Save Playlist...")];
548 [o_mi_play setTitle: _NS("Play")];
549 [o_mi_delete setTitle: _NS("Delete")];
550 [o_mi_recursive_expand setTitle: _NS("Expand Node")];
551 [o_mi_selectall setTitle: _NS("Select All")];
552 [o_mi_info setTitle: _NS("Media Information...")];
553 [o_mi_dl_cover_art setTitle: _NS("Download Cover Art")];
554 [o_mi_preparse setTitle: _NS("Fetch Meta Data")];
555 [o_mi_revealInFinder setTitle: _NS("Reveal in Finder")];
556 [o_mm_mi_revealInFinder setTitle: _NS("Reveal in Finder")];
557 [[o_mm_mi_revealInFinder menu] setAutoenablesItems: NO];
558 [o_mi_sort_name setTitle: _NS("Sort Node by Name")];
559 [o_mi_sort_author setTitle: _NS("Sort Node by Author")];
561 [o_search_field setToolTip: _NS("Search in Playlist")];
562 [o_search_field_other setToolTip: _NS("Search in Playlist")];
564 [o_save_accessory_text setStringValue: _NS("File Format:")];
565 [[o_save_accessory_popup itemAtIndex:0] setTitle: _NS("Extended M3U")];
566 [[o_save_accessory_popup itemAtIndex:1] setTitle: _NS("XML Shareable Playlist Format (XSPF)")];
567 [[o_save_accessory_popup itemAtIndex:2] setTitle: _NS("HTML Playlist")];
570 - (void)playlistUpdated
572 /* Clear indications of any existing column sorting */
573 NSUInteger count = [[o_outline_view tableColumns] count];
574 for( NSUInteger i = 0 ; i < count ; i++ )
576 [o_outline_view setIndicatorImage:nil inTableColumn:
577 [[o_outline_view tableColumns] objectAtIndex:i]];
580 [o_outline_view setHighlightedTableColumn:nil];
581 o_tc_sortColumn = nil;
582 // TODO Find a way to keep the dict size to a minimum
583 //[o_outline_dict removeAllObjects];
584 [o_outline_view reloadData];
585 [[[[VLCMain sharedInstance] wizard] playlistWizard] reloadOutlineView];
586 [[[[VLCMain sharedInstance] bookmarks] dataTable] reloadData];
588 [self outlineViewSelectionDidChange: nil];
589 [[VLCMain sharedInstance] updateMainWindow];
592 - (void)outlineViewSelectionDidChange:(NSNotification *)notification
595 playlist_item_t * p_item = [[o_outline_view itemAtRow:[o_outline_view selectedRow]] pointerValue];
599 /* update the state of our Reveal-in-Finder menu items */
600 NSMutableString *o_mrl;
601 char *psz_uri = input_item_GetURI( p_item->p_input );
603 [o_mi_revealInFinder setEnabled: NO];
604 [o_mm_mi_revealInFinder setEnabled: NO];
607 o_mrl = [NSMutableString stringWithUTF8String: psz_uri];
609 /* perform some checks whether it is a file and if it is local at all... */
610 NSRange prefix_range = [o_mrl rangeOfString: @"file:"];
611 if( prefix_range.location != NSNotFound )
612 [o_mrl deleteCharactersInRange: prefix_range];
614 if( [o_mrl characterAtIndex:0] == '/' )
616 [o_mi_revealInFinder setEnabled: YES];
617 [o_mm_mi_revealInFinder setEnabled: YES];
622 /* update our info-panel to reflect the new item */
623 [[[VLCMain sharedInstance] info] updatePanelWithItem:p_item->p_input];
627 - (BOOL)isSelectionEmpty
629 return [o_outline_view selectedRow] == -1;
632 - (void)updateRowSelection
635 playlist_t *p_playlist = pl_Get( VLCIntf );
636 playlist_item_t *p_item, *p_temp_item;
637 NSMutableArray *o_array = [NSMutableArray array];
640 p_item = playlist_CurrentPlayingItem( p_playlist );
647 p_temp_item = p_item;
648 while( p_temp_item->p_parent )
650 [o_array insertObject: [NSValue valueWithPointer: p_temp_item] atIndex: 0];
651 p_temp_item = p_temp_item->p_parent;
655 NSUInteger count = [o_array count];
656 for( NSUInteger j = 0; j < count - 1; j++ )
659 if( ( o_item = [o_outline_dict objectForKey:
660 [NSString stringWithFormat: @"%p",
661 [[o_array objectAtIndex:j] pointerValue]]] ) != nil )
663 [o_outline_view expandItem: o_item];
667 id o_item = [o_outline_dict objectForKey:[NSString stringWithFormat: @"%p", p_item]];
668 NSInteger i_index = [o_outline_view rowForItem:o_item];
669 [o_outline_view selectRowIndexes:[NSIndexSet indexSetWithIndex:i_index] byExtendingSelection:NO];
670 [o_outline_view setNeedsDisplay:YES];
673 /* Check if p_item is a child of p_node recursively. We need to check the item
674 existence first since OSX sometimes tries to redraw items that have been
675 deleted. We don't do it when not required since this verification takes
676 quite a long time on big playlists (yes, pretty hacky). */
678 - (BOOL)isItem: (playlist_item_t *)p_item
679 inNode: (playlist_item_t *)p_node
680 checkItemExistence:(BOOL)b_check
681 locked:(BOOL)b_locked
684 playlist_t * p_playlist = pl_Get( VLCIntf );
685 playlist_item_t *p_temp_item = p_item;
687 if( p_node == p_item )
690 if( p_node->i_children < 1)
696 if(!b_locked) PL_LOCK;
700 /* Since outlineView: willDisplayCell:... may call this function with
701 p_items that don't exist anymore, first check if the item is still
702 in the playlist. Any cleaner solution welcomed. */
703 for( i = 0; i < p_playlist->all_items.i_size; i++ )
705 if( ARRAY_VAL( p_playlist->all_items, i) == p_item ) break;
706 else if ( i == p_playlist->all_items.i_size - 1 )
708 if(!b_locked) PL_UNLOCK;
716 p_temp_item = p_temp_item->p_parent;
717 if( p_temp_item == p_node )
719 if(!b_locked) PL_UNLOCK;
723 if(!b_locked) PL_UNLOCK;
728 - (BOOL)isItem: (playlist_item_t *)p_item
729 inNode: (playlist_item_t *)p_node
730 checkItemExistence:(BOOL)b_check
732 return [self isItem:p_item inNode:p_node checkItemExistence:b_check locked:NO];
735 /* This method is useful for instance to remove the selected children of an
736 already selected node */
737 - (void)removeItemsFrom:(id)o_items ifChildrenOf:(id)o_nodes
739 NSUInteger itemCount = [o_items count];
740 NSUInteger nodeCount = [o_nodes count];
741 for( NSUInteger i = 0 ; i < itemCount ; i++ )
743 for ( NSUInteger j = 0 ; j < nodeCount ; j++ )
745 if( o_items == o_nodes)
747 if( j == i ) continue;
749 if( [self isItem: [[o_items objectAtIndex:i] pointerValue]
750 inNode: [[o_nodes objectAtIndex:j] pointerValue]
751 checkItemExistence: NO locked:NO] )
753 [o_items removeObjectAtIndex:i];
754 /* We need to execute the next iteration with the same index
755 since the current item has been deleted */
763 - (IBAction)savePlaylist:(id)sender
765 playlist_t * p_playlist = pl_Get( VLCIntf );
767 NSSavePanel *o_save_panel = [NSSavePanel savePanel];
768 NSString * o_name = [NSString stringWithFormat: @"%@", _NS("Untitled")];
770 [o_save_panel setTitle: _NS("Save Playlist")];
771 [o_save_panel setPrompt: _NS("Save")];
772 [o_save_panel setAccessoryView: o_save_accessory_view];
774 if( [o_save_panel runModalForDirectory: nil
775 file: o_name] == NSOKButton )
777 NSString *o_filename = [[o_save_panel URL] path];
779 if( [o_save_accessory_popup indexOfSelectedItem] == 0 )
781 NSString * o_real_filename;
783 range.location = [o_filename length] - [@".m3u" length];
784 range.length = [@".m3u" length];
786 if( [o_filename compare:@".m3u" options: NSCaseInsensitiveSearch
787 range: range] != NSOrderedSame )
789 o_real_filename = [NSString stringWithFormat: @"%@.m3u", o_filename];
793 o_real_filename = o_filename;
795 playlist_Export( p_playlist,
796 [o_real_filename fileSystemRepresentation],
797 p_playlist->p_local_category, "export-m3u" );
799 else if( [o_save_accessory_popup indexOfSelectedItem] == 1 )
801 NSString * o_real_filename;
803 range.location = [o_filename length] - [@".xspf" length];
804 range.length = [@".xspf" length];
806 if( [o_filename compare:@".xspf" options: NSCaseInsensitiveSearch
807 range: range] != NSOrderedSame )
809 o_real_filename = [NSString stringWithFormat: @"%@.xspf", o_filename];
813 o_real_filename = o_filename;
815 playlist_Export( p_playlist,
816 [o_real_filename fileSystemRepresentation],
817 p_playlist->p_local_category, "export-xspf" );
821 NSString * o_real_filename;
823 range.location = [o_filename length] - [@".html" length];
824 range.length = [@".html" length];
826 if( [o_filename compare:@".html" options: NSCaseInsensitiveSearch
827 range: range] != NSOrderedSame )
829 o_real_filename = [NSString stringWithFormat: @"%@.html", o_filename];
833 o_real_filename = o_filename;
835 playlist_Export( p_playlist,
836 [o_real_filename fileSystemRepresentation],
837 p_playlist->p_local_category, "export-html" );
842 /* When called retrieves the selected outlineview row and plays that node or item */
843 - (IBAction)playItem:(id)sender
845 intf_thread_t * p_intf = VLCIntf;
846 playlist_t * p_playlist = pl_Get( p_intf );
848 playlist_item_t *p_item;
849 playlist_item_t *p_node = NULL;
851 // ignore clicks on column header when handling double action
852 if( sender != nil && [o_outline_view clickedRow] == -1 )
855 p_item = [[o_outline_view itemAtRow:[o_outline_view selectedRow]] pointerValue];
860 if( p_item->i_children == -1 )
862 p_node = p_item->p_parent;
867 if( p_node->i_children > 0 && p_node->pp_children[0]->i_children == -1 )
869 p_item = p_node->pp_children[0];
876 playlist_Control( p_playlist, PLAYLIST_VIEWPLAY, pl_Locked, p_node, p_item );
881 - (IBAction)revealItemInFinder:(id)sender
883 playlist_item_t * p_item = [[o_outline_view itemAtRow:[o_outline_view selectedRow]] pointerValue];
884 NSMutableString * o_mrl = nil;
886 if(! p_item || !p_item->p_input )
889 char *psz_uri = decode_URI( input_item_GetURI( p_item->p_input ) );
891 o_mrl = [NSMutableString stringWithUTF8String: psz_uri];
893 /* perform some checks whether it is a file and if it is local at all... */
894 NSRange prefix_range = [o_mrl rangeOfString: @"file:"];
895 if( prefix_range.location != NSNotFound )
896 [o_mrl deleteCharactersInRange: prefix_range];
898 if( [o_mrl characterAtIndex:0] == '/' )
899 [[NSWorkspace sharedWorkspace] selectFile: o_mrl inFileViewerRootedAtPath: o_mrl];
902 /* When called retrieves the selected outlineview row and plays that node or item */
903 - (IBAction)preparseItem:(id)sender
906 NSIndexSet *o_selected_indexes;
907 intf_thread_t * p_intf = VLCIntf;
908 playlist_t * p_playlist = pl_Get( p_intf );
909 playlist_item_t *p_item = NULL;
911 o_selected_indexes = [o_outline_view selectedRowIndexes];
912 i_count = [o_selected_indexes count];
914 NSUInteger indexes[i_count];
915 [o_selected_indexes getIndexes:indexes maxCount:i_count inIndexRange:nil];
916 for (int i = 0; i < i_count; i++)
918 p_item = [[o_outline_view itemAtRow:indexes[i]] pointerValue];
919 [o_outline_view deselectRow: indexes[i]];
923 if( p_item->i_children == -1 )
924 playlist_PreparseEnqueue( p_playlist, p_item->p_input );
926 msg_Dbg( p_intf, "preparsing nodes not implemented" );
929 [self playlistUpdated];
932 - (IBAction)downloadCoverArt:(id)sender
935 NSIndexSet *o_selected_indexes;
936 intf_thread_t * p_intf = VLCIntf;
937 playlist_t * p_playlist = pl_Get( p_intf );
938 playlist_item_t *p_item = NULL;
940 o_selected_indexes = [o_outline_view selectedRowIndexes];
941 i_count = [o_selected_indexes count];
943 NSUInteger indexes[i_count];
944 [o_selected_indexes getIndexes:indexes maxCount:i_count inIndexRange:nil];
945 for (int i = 0; i < i_count; i++)
947 p_item = [[o_outline_view itemAtRow: indexes[i]] pointerValue];
948 [o_outline_view deselectRow: indexes[i]];
950 if( p_item && p_item->i_children == -1 )
951 playlist_AskForArtEnqueue( p_playlist, p_item->p_input );
953 [self playlistUpdated];
956 - (IBAction)selectAll:(id)sender
958 [o_outline_view selectAll: nil];
961 - (IBAction)deleteItem:(id)sender
964 NSIndexSet *o_selected_indexes;
965 playlist_t * p_playlist;
966 intf_thread_t * p_intf = VLCIntf;
968 o_selected_indexes = [o_outline_view selectedRowIndexes];
969 i_count = [o_selected_indexes count];
971 p_playlist = pl_Get( p_intf );
973 NSUInteger indexes[i_count];
974 if (i_count == [o_outline_view numberOfRows])
977 msg_Dbg( p_intf, "user selected entire list, deleting current playlist root instead of individual items" );
980 playlist_NodeDelete( p_playlist, [self currentPlaylistRoot], true, false );
982 [self playlistUpdated];
985 [o_selected_indexes getIndexes:indexes maxCount:i_count inIndexRange:nil];
986 for (int i = 0; i < i_count; i++)
988 id o_item = [o_outline_view itemAtRow: indexes[i]];
989 [o_outline_view deselectRow: indexes[i]];
992 playlist_item_t *p_item = [o_item pointerValue];
994 msg_Dbg( p_intf, "deleting item %i (of %i) with id \"%i\", pointerValue \"%p\" and %i children", i+1, i_count,
995 p_item->p_input->i_id, [o_item pointerValue], p_item->i_children +1 );
998 if( p_item->i_children != -1 )
999 //is a node and not an item
1001 if( playlist_Status( p_playlist ) != PLAYLIST_STOPPED &&
1002 [self isItem: playlist_CurrentPlayingItem( p_playlist ) inNode: ((playlist_item_t *)[o_item pointerValue])
1003 checkItemExistence: NO locked:YES] == YES )
1004 // if current item is in selected node and is playing then stop playlist
1005 playlist_Control(p_playlist, PLAYLIST_STOP, pl_Locked );
1007 playlist_NodeDelete( p_playlist, p_item, true, false );
1010 playlist_DeleteFromInput( p_playlist, p_item->p_input, pl_Locked );
1013 [o_outline_dict removeObjectForKey:[NSString stringWithFormat:@"%p", [o_item pointerValue]]];
1017 [self playlistUpdated];
1020 - (IBAction)sortNodeByName:(id)sender
1022 [self sortNode: SORT_TITLE];
1025 - (IBAction)sortNodeByAuthor:(id)sender
1027 [self sortNode: SORT_ARTIST];
1030 - (void)sortNode:(int)i_mode
1032 playlist_t * p_playlist = pl_Get( VLCIntf );
1033 playlist_item_t * p_item;
1035 if( [o_outline_view selectedRow] > -1 )
1037 p_item = [[o_outline_view itemAtRow: [o_outline_view selectedRow]] pointerValue];
1040 /*If no item is selected, sort the whole playlist*/
1042 p_item = [self currentPlaylistRoot];
1046 if( p_item->i_children > -1 ) // the item is a node
1048 playlist_RecursiveNodeSort( p_playlist, p_item, i_mode, ORDER_NORMAL );
1052 playlist_RecursiveNodeSort( p_playlist,
1053 p_item->p_parent, i_mode, ORDER_NORMAL );
1056 [self playlistUpdated];
1059 - (input_item_t *)createItem:(NSDictionary *)o_one_item
1061 intf_thread_t * p_intf = VLCIntf;
1062 playlist_t * p_playlist = pl_Get( p_intf );
1064 input_item_t *p_input;
1065 BOOL b_rem = FALSE, b_dir = FALSE, b_writable = FALSE;
1066 NSString *o_uri, *o_name, *o_path;
1072 o_uri = (NSString *)[o_one_item objectForKey: @"ITEM_URL"];
1073 o_nsurl = [NSURL URLWithString: o_uri];
1074 o_path = [o_nsurl path];
1075 o_name = (NSString *)[o_one_item objectForKey: @"ITEM_NAME"];
1076 o_options = (NSArray *)[o_one_item objectForKey: @"ITEM_OPTIONS"];
1078 if( [[NSFileManager defaultManager] fileExistsAtPath:o_path isDirectory:&b_dir] && b_dir &&
1079 [[NSWorkspace sharedWorkspace] getFileSystemInfoForPath:o_path isRemovable: &b_rem
1080 isWritable:&b_writable isUnmountable:NULL description:NULL type:NULL] && b_rem && !b_writable && [o_nsurl isFileURL] )
1083 id o_vlc_open = [[VLCMain sharedInstance] open];
1085 char *diskType = [o_vlc_open getVolumeTypeFromMountPath: o_path];
1086 msg_Dbg( p_intf, "detected optical media of type '%s' in the file input", diskType );
1088 if (diskType == kVLCMediaDVD)
1090 o_uri = [NSString stringWithFormat: @"dvdnav://%@", [o_vlc_open getBSDNodeFromMountPath: o_path]];
1092 else if (diskType == kVLCMediaVideoTSFolder)
1094 o_uri = [NSString stringWithFormat: @"dvdnav://%@", o_path];
1096 else if (diskType == kVLCMediaAudioCD)
1098 o_uri = [NSString stringWithFormat: @"cdda://%@", [o_vlc_open getBSDNodeFromMountPath: o_path]];
1100 else if (diskType == kVLCMediaVCD)
1102 o_uri = [NSString stringWithFormat: @"vcd://%@#0:0", [o_vlc_open getBSDNodeFromMountPath: o_path]];
1104 else if (diskType == kVLCMediaSVCD)
1106 o_uri = [NSString stringWithFormat: @"vcd://%@@0:0", [o_vlc_open getBSDNodeFromMountPath: o_path]];
1108 else if (diskType == kVLCMediaBD || diskType == kVLCMediaBDMVFolder)
1110 o_uri = [NSString stringWithFormat: @"bluray://%@", o_path];
1114 msg_Warn( VLCIntf, "unknown disk type, treating %s as regular input", [o_path UTF8String] );
1117 p_input = input_item_New( [o_uri UTF8String], [[[NSFileManager defaultManager] displayNameAtPath: o_path] UTF8String] );
1120 p_input = input_item_New( [o_uri fileSystemRepresentation], o_name ? [o_name UTF8String] : NULL );
1127 NSUInteger count = [o_options count];
1128 for( NSUInteger i = 0; i < count; i++ )
1130 input_item_AddOption( p_input, [[o_options objectAtIndex:i] UTF8String],
1131 VLC_INPUT_OPTION_TRUSTED );
1135 /* Recent documents menu */
1136 if( o_nsurl != nil && (BOOL)config_GetInt( p_playlist, "macosx-recentitems" ) == YES )
1138 [[NSDocumentController sharedDocumentController] noteNewRecentDocumentURL: o_nsurl];
1143 - (void)appendArray:(NSArray*)o_array atPos:(int)i_position enqueue:(BOOL)b_enqueue
1145 playlist_t * p_playlist = pl_Get( VLCIntf );
1146 NSUInteger count = [o_array count];
1147 BOOL b_usingPlaylist;
1148 if ([self currentPlaylistRoot] == p_playlist->p_ml_category)
1149 b_usingPlaylist = NO;
1151 b_usingPlaylist = YES;
1154 for( NSUInteger i_item = 0; i_item < count; i_item++ )
1156 input_item_t *p_input;
1157 NSDictionary *o_one_item;
1160 o_one_item = [o_array objectAtIndex: i_item];
1161 p_input = [self createItem: o_one_item];
1168 /* FIXME: playlist_AddInput() can fail */
1170 playlist_AddInput( p_playlist, p_input, PLAYLIST_INSERT, i_position == -1 ? PLAYLIST_END : i_position + i_item, b_usingPlaylist,
1173 if( i_item == 0 && !b_enqueue )
1175 playlist_item_t *p_item = playlist_ItemGetByInput( p_playlist, p_input );
1176 playlist_Control( p_playlist, PLAYLIST_VIEWPLAY, pl_Locked, p_item->p_parent, p_item );
1179 vlc_gc_decref( p_input );
1182 [self playlistUpdated];
1185 - (void)appendNodeArray:(NSArray*)o_array inNode:(playlist_item_t *)p_node atPos:(int)i_position enqueue:(BOOL)b_enqueue
1187 playlist_t * p_playlist = pl_Get( VLCIntf );
1188 NSUInteger count = [o_array count];
1190 for( NSUInteger i_item = 0; i_item < count; i_item++ )
1192 input_item_t *p_input;
1193 NSDictionary *o_one_item;
1196 o_one_item = [o_array objectAtIndex: i_item];
1197 p_input = [self createItem: o_one_item];
1199 if( !p_input ) continue;
1203 playlist_NodeAddInput( p_playlist, p_input, p_node,
1206 PLAYLIST_END : i_position + i_item,
1210 if( i_item == 0 && !b_enqueue )
1212 playlist_item_t *p_item;
1213 p_item = playlist_ItemGetByInput( p_playlist, p_input );
1214 playlist_Control( p_playlist, PLAYLIST_VIEWPLAY, pl_Locked, p_node, p_item );
1217 vlc_gc_decref( p_input );
1219 [self playlistUpdated];
1222 - (NSMutableArray *)subSearchItem:(playlist_item_t *)p_item
1224 playlist_t *p_playlist = pl_Get( VLCIntf );
1225 playlist_item_t *p_selected_item;
1228 i_selected_row = [o_outline_view selectedRow];
1229 if (i_selected_row < 0)
1232 p_selected_item = (playlist_item_t *)[[o_outline_view itemAtRow: i_selected_row] pointerValue];
1234 for( NSUInteger i_current = 0; i_current < p_item->i_children ; i_current++ )
1237 NSString *o_current_name, *o_current_author;
1240 o_current_name = [NSString stringWithUTF8String:
1241 p_item->pp_children[i_current]->p_input->psz_name];
1242 psz_temp = input_item_GetInfo( p_item->p_input ,
1243 _("Meta-information"),_("Artist") );
1244 o_current_author = [NSString stringWithUTF8String: psz_temp];
1248 if( p_selected_item == p_item->pp_children[i_current] &&
1249 b_selected_item_met == NO )
1251 b_selected_item_met = YES;
1253 else if( p_selected_item == p_item->pp_children[i_current] &&
1254 b_selected_item_met == YES )
1258 else if( b_selected_item_met == YES &&
1259 ( [o_current_name rangeOfString:[o_search_field
1260 stringValue] options:NSCaseInsensitiveSearch].length ||
1261 [o_current_author rangeOfString:[o_search_field
1262 stringValue] options:NSCaseInsensitiveSearch].length ) )
1264 /*Adds the parent items in the result array as well, so that we can
1266 return [NSMutableArray arrayWithObject: [NSValue
1267 valueWithPointer: p_item->pp_children[i_current]]];
1269 if( p_item->pp_children[i_current]->i_children > 0 )
1271 id o_result = [self subSearchItem:
1272 p_item->pp_children[i_current]];
1273 if( o_result != NULL )
1275 [o_result insertObject: [NSValue valueWithPointer:
1276 p_item->pp_children[i_current]] atIndex:0];
1284 - (IBAction)searchItem:(id)sender
1286 playlist_t * p_playlist = pl_Get( VLCIntf );
1291 b_selected_item_met = NO;
1293 /*First, only search after the selected item:*
1294 *(b_selected_item_met = NO) */
1295 o_result = [self subSearchItem:[self currentPlaylistRoot]];
1296 if( o_result == NULL )
1298 /* If the first search failed, search again from the beginning */
1299 o_result = [self subSearchItem:[self currentPlaylistRoot]];
1301 if( o_result != NULL )
1304 if( [[o_result objectAtIndex: 0] pointerValue] == p_playlist->p_local_category )
1308 NSUInteger count = [o_result count];
1310 for( NSUInteger i = i_start ; i < count - 1 ; i++ )
1312 [o_outline_view expandItem: [o_outline_dict objectForKey:
1313 [NSString stringWithFormat: @"%p",
1314 [[o_result objectAtIndex: i] pointerValue]]]];
1316 i_row = [o_outline_view rowForItem: [o_outline_dict objectForKey:
1317 [NSString stringWithFormat: @"%p",
1318 [[o_result objectAtIndex: count - 1 ]
1323 [o_outline_view selectRowIndexes:[NSIndexSet indexSetWithIndex:i_row] byExtendingSelection:NO];
1324 [o_outline_view scrollRowToVisible: i_row];
1328 - (IBAction)recursiveExpandNode:(id)sender
1330 id o_item = [o_outline_view itemAtRow: [o_outline_view selectedRow]];
1331 playlist_item_t *p_item = (playlist_item_t *)[o_item pointerValue];
1333 if( ![[o_outline_view dataSource] outlineView: o_outline_view
1334 isItemExpandable: o_item] )
1336 o_item = [o_outline_dict objectForKey: [NSString stringWithFormat: @"%p", p_item->p_parent]];
1339 /* We need to collapse the node first, since OSX refuses to recursively
1340 expand an already expanded node, even if children nodes are collapsed. */
1341 [o_outline_view collapseItem: o_item collapseChildren: YES];
1342 [o_outline_view expandItem: o_item expandChildren: YES];
1345 - (NSMenu *)menuForEvent:(NSEvent *)o_event
1351 pt = [o_outline_view convertPoint: [o_event locationInWindow]
1353 int row = [o_outline_view rowAtPoint:pt];
1355 [o_outline_view selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO];
1357 b_item_sel = ( row != -1 && [o_outline_view selectedRow] != -1 );
1358 b_rows = [o_outline_view numberOfRows] != 0;
1360 [o_mi_play setEnabled: b_item_sel];
1361 [o_mi_delete setEnabled: b_item_sel];
1362 [o_mi_selectall setEnabled: b_rows];
1363 [o_mi_info setEnabled: b_item_sel];
1364 [o_mi_preparse setEnabled: b_item_sel];
1365 [o_mi_recursive_expand setEnabled: b_item_sel];
1366 [o_mi_sort_name setEnabled: b_item_sel];
1367 [o_mi_sort_author setEnabled: b_item_sel];
1369 return( o_ctx_menu );
1372 - (void)outlineView: (NSOutlineView *)o_tv
1373 didClickTableColumn:(NSTableColumn *)o_tc
1375 int i_mode, i_type = 0;
1376 intf_thread_t *p_intf = VLCIntf;
1377 NSString * o_identifier = [o_tc identifier];
1379 playlist_t *p_playlist = pl_Get( p_intf );
1381 /* Check whether the selected table column header corresponds to a
1382 sortable table column*/
1383 if( !( [o_identifier isEqualToString:TITLE_COLUMN] || [o_identifier isEqualToString:ARTIST_COLUMN] || [o_identifier isEqualToString:DURATION_COLUMN] ) )
1386 if( o_tc_sortColumn == o_tc )
1387 b_isSortDescending = !b_isSortDescending;
1389 b_isSortDescending = false;
1391 if( [o_identifier isEqualToString:TITLE_COLUMN] )
1392 i_mode = SORT_TITLE;
1393 else if( [o_identifier isEqualToString:ARTIST_COLUMN] )
1394 i_mode = SORT_ARTIST;
1395 else if( [o_identifier isEqualToString:DURATION_COLUMN] )
1396 i_mode = SORT_DURATION;
1398 if( b_isSortDescending )
1399 i_type = ORDER_REVERSE;
1401 i_type = ORDER_NORMAL;
1404 playlist_RecursiveNodeSort( p_playlist, [self currentPlaylistRoot], i_mode, i_type );
1407 [self playlistUpdated];
1409 o_tc_sortColumn = o_tc;
1410 [o_outline_view setHighlightedTableColumn:o_tc];
1412 if( b_isSortDescending )
1413 [o_outline_view setIndicatorImage:o_descendingSortingImage inTableColumn:o_tc];
1415 [o_outline_view setIndicatorImage:o_ascendingSortingImage inTableColumn:o_tc];
1419 - (void)outlineView:(NSOutlineView *)outlineView
1420 willDisplayCell:(id)cell
1421 forTableColumn:(NSTableColumn *)tableColumn
1424 /* this method can be called when VLC is already dead, hence the extra checks */
1425 intf_thread_t * p_intf = VLCIntf;
1428 playlist_t *p_playlist = pl_Get( p_intf );
1435 o_playing_item = [o_outline_dict objectForKey: [NSString stringWithFormat:@"%p", playlist_CurrentPlayingItem( p_playlist )]];
1438 if( [self isItem: [o_playing_item pointerValue] inNode:
1439 [item pointerValue] checkItemExistence: YES]
1440 || [o_playing_item isEqual: item] )
1442 [cell setFont: [[NSFontManager sharedFontManager] convertFont:[cell font] toHaveTrait:NSBoldFontMask]];
1446 [cell setFont: [[NSFontManager sharedFontManager] convertFont:[cell font] toNotHaveTrait:NSBoldFontMask]];
1452 playlist_t *p_playlist = pl_Get( VLCIntf );
1457 o_playing_item = [o_outline_dict objectForKey: [NSString stringWithFormat:@"%p", playlist_CurrentPlayingItem( p_playlist )]];
1460 return o_playing_item;
1463 - (NSArray *)draggedItems
1465 return [[o_nodes_array arrayByAddingObjectsFromArray: o_items_array] retain];
1469 @implementation VLCPlaylist (NSOutlineViewDataSource)
1471 - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item
1473 id o_value = [super outlineView: outlineView child: index ofItem: item];
1475 [o_outline_dict setObject:o_value forKey:[NSString stringWithFormat:@"%p", [o_value pointerValue]]];
1479 /* Required for drag & drop and reordering */
1480 - (BOOL)outlineView:(NSOutlineView *)outlineView writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pboard
1482 playlist_t *p_playlist = pl_Get( VLCIntf );
1484 /* First remove the items that were moved during the last drag & drop
1486 [o_items_array removeAllObjects];
1487 [o_nodes_array removeAllObjects];
1489 NSUInteger itemCount = [items count];
1491 for( NSUInteger i = 0 ; i < itemCount ; i++ )
1493 id o_item = [items objectAtIndex: i];
1495 /* Fill the items and nodes to move in 2 different arrays */
1496 if( ((playlist_item_t *)[o_item pointerValue])->i_children > 0 )
1497 [o_nodes_array addObject: o_item];
1499 [o_items_array addObject: o_item];
1502 /* Now we need to check if there are selected items that are in already
1503 selected nodes. In that case, we only want to move the nodes */
1504 [self removeItemsFrom: o_nodes_array ifChildrenOf: o_nodes_array];
1505 [self removeItemsFrom: o_items_array ifChildrenOf: o_nodes_array];
1507 /* We add the "VLCPlaylistItemPboardType" type to be able to recognize
1508 a Drop operation coming from the playlist. */
1510 [pboard declareTypes: [NSArray arrayWithObjects:
1511 @"VLCPlaylistItemPboardType", nil] owner: self];
1512 [pboard setData:[NSData data] forType:@"VLCPlaylistItemPboardType"];
1517 - (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id <NSDraggingInfo>)info proposedItem:(id)item proposedChildIndex:(NSInteger)index
1519 playlist_t *p_playlist = pl_Get( VLCIntf );
1520 NSPasteboard *o_pasteboard = [info draggingPasteboard];
1522 if( !p_playlist ) return NSDragOperationNone;
1524 /* Dropping ON items is not allowed if item is not a node */
1527 if( index == NSOutlineViewDropOnItemIndex &&
1528 ((playlist_item_t *)[item pointerValue])->i_children == -1 )
1530 return NSDragOperationNone;
1534 /* We refuse to drop an item in anything else than a child of the General
1535 Node. We still accept items that would be root nodes of the outlineview
1536 however, to allow drop in an empty playlist. */
1537 if( !( ([self isItem: [item pointerValue] inNode: p_playlist->p_local_category checkItemExistence: NO] ||
1538 ( var_CreateGetBool( p_playlist, "media-library" ) && [self isItem: [item pointerValue] inNode: p_playlist->p_ml_category checkItemExistence: NO] ) ) || item == nil ) )
1540 return NSDragOperationNone;
1543 /* Drop from the Playlist */
1544 if( [[o_pasteboard types] containsObject: @"VLCPlaylistItemPboardType"] )
1546 NSUInteger count = [o_nodes_array count];
1547 for( NSUInteger i = 0 ; i < count ; i++ )
1549 /* We refuse to Drop in a child of an item we are moving */
1550 if( [self isItem: [item pointerValue] inNode:
1551 [[o_nodes_array objectAtIndex: i] pointerValue]
1552 checkItemExistence: NO] )
1554 return NSDragOperationNone;
1557 return NSDragOperationMove;
1560 /* Drop from the Finder */
1561 else if( [[o_pasteboard types] containsObject: NSFilenamesPboardType] )
1563 return NSDragOperationGeneric;
1565 return NSDragOperationNone;
1568 - (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id <NSDraggingInfo>)info item:(id)item childIndex:(NSInteger)index
1570 playlist_t * p_playlist = pl_Get( VLCIntf );
1571 NSPasteboard *o_pasteboard = [info draggingPasteboard];
1573 /* Drag & Drop inside the playlist */
1574 if( [[o_pasteboard types] containsObject: @"VLCPlaylistItemPboardType"] )
1576 int i_row, i_removed_from_node = 0;
1577 playlist_item_t *p_new_parent, *p_item = NULL;
1578 NSArray *o_all_items = [o_nodes_array arrayByAddingObjectsFromArray: o_items_array];
1579 /* If the item is to be dropped as root item of the outline, make it a
1580 child of the respective general node, if is either the pl or the ml
1581 Else, choose the proposed parent as parent. */
1584 if ([self currentPlaylistRoot] == p_playlist->p_local_category || [self currentPlaylistRoot] == p_playlist->p_ml_category)
1585 p_new_parent = [self currentPlaylistRoot];
1590 p_new_parent = [item pointerValue];
1592 /* Make sure the proposed parent is a node.
1593 (This should never be true) */
1594 if( p_new_parent->i_children < 0 )
1599 NSUInteger count = [o_all_items count];
1600 for( NSUInteger i = 0; i < count; i++ )
1602 playlist_item_t *p_old_parent = NULL;
1603 int i_old_index = 0;
1605 p_item = [[o_all_items objectAtIndex:i] pointerValue];
1606 p_old_parent = p_item->p_parent;
1609 /* We may need the old index later */
1610 if( p_new_parent == p_old_parent )
1612 for( NSInteger j = 0; j < p_old_parent->i_children; j++ )
1614 if( p_old_parent->pp_children[j] == p_item )
1623 // Actually detach the item from the old position
1624 if( playlist_NodeRemoveItem( p_playlist, p_item, p_old_parent ) ==
1628 /* Calculate the new index */
1631 /* If we move the item in the same node, we need to take into
1632 account that one item will be deleted */
1635 if ((p_new_parent == p_old_parent && i_old_index < index + (int)i) )
1637 i_removed_from_node++;
1639 i_new_index = index + i - i_removed_from_node;
1641 // Reattach the item to the new position
1642 playlist_NodeInsert( p_playlist, p_item, p_new_parent, i_new_index );
1646 [self playlistUpdated];
1647 i_row = [o_outline_view rowForItem:[o_outline_dict objectForKey:[NSString stringWithFormat: @"%p", [[o_all_items objectAtIndex: 0] pointerValue]]]];
1651 i_row = [o_outline_view rowForItem:[o_outline_dict objectForKey:[NSString stringWithFormat: @"%p", p_new_parent]]];
1654 [o_outline_view deselectAll: self];
1655 [o_outline_view selectRowIndexes:[NSIndexSet indexSetWithIndex:i_row] byExtendingSelection:NO];
1656 [o_outline_view scrollRowToVisible: i_row];
1661 else if( [[o_pasteboard types] containsObject: NSFilenamesPboardType] )
1663 if ([self currentPlaylistRoot] != p_playlist->p_local_category && [self currentPlaylistRoot] != p_playlist->p_ml_category)
1666 playlist_item_t *p_node = [item pointerValue];
1668 NSArray *o_values = [[o_pasteboard propertyListForType: NSFilenamesPboardType]
1669 sortedArrayUsingSelector: @selector(caseInsensitiveCompare:)];
1670 NSUInteger count = [o_values count];
1671 NSMutableArray *o_array = [NSMutableArray arrayWithCapacity:count];
1672 input_thread_t * p_input = pl_CurrentInput( VLCIntf );
1673 BOOL b_returned = NO;
1675 if (count == 1 && p_input)
1677 b_returned = input_AddSubtitle( p_input, make_URI([[o_values objectAtIndex:0] UTF8String], NULL), true );
1678 vlc_object_release( p_input );
1683 vlc_object_release( p_input );
1685 for( NSUInteger i = 0; i < count; i++)
1687 NSDictionary *o_dic;
1688 char *psz_uri = make_URI([[o_values objectAtIndex:i] UTF8String], NULL);
1692 o_dic = [NSDictionary dictionaryWithObject:[NSString stringWithCString:psz_uri encoding:NSUTF8StringEncoding] forKey:@"ITEM_URL"];
1696 [o_array addObject: o_dic];
1701 [self appendArray:o_array atPos:index enqueue: YES];
1705 assert( p_node->i_children != -1 );
1706 [self appendNodeArray:o_array inNode: p_node atPos:index enqueue:YES];