macosx: compilation fix for earlier clang releases and runtime fix for 10.6
[vlc.git] / modules / gui / macosx / playlist.m
1 /*****************************************************************************
2  * playlist.m: MacOS X interface module
3  *****************************************************************************
4 * Copyright (C) 2002-2014 VLC authors and VideoLAN
5  * $Id$
6  *
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>
11  *
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.
16  *
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.
21  *
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  *****************************************************************************/
26
27 /* TODO
28  * add 'icons' for different types of nodes? (http://www.cocoadev.com/index.pl?IconAndTextInTableCell)
29  * reimplement enable/disable item
30  */
31
32
33 /*****************************************************************************
34  * Preamble
35  *****************************************************************************/
36 #include <stdlib.h>                                      /* malloc(), free() */
37 #include <sys/param.h>                                    /* for MAXPATHLEN */
38 #include <string.h>
39 #include <math.h>
40 #include <sys/mount.h>
41
42 #import "CompatibilityFixes.h"
43
44 #import "intf.h"
45 #import "wizard.h"
46 #import "bookmarks.h"
47 #import "playlistinfo.h"
48 #import "playlist.h"
49 #import "controls.h"
50 #import "misc.h"
51 #import "open.h"
52 #import "MainMenu.h"
53 #import "CoreInteraction.h"
54
55 #include <vlc_keys.h>
56 #import <vlc_interface.h>
57 #include <vlc_url.h>
58
59 /*****************************************************************************
60  * VLCPlaylistView implementation
61  *****************************************************************************/
62 @implementation VLCPlaylistView
63
64 - (NSMenu *)menuForEvent:(NSEvent *)o_event
65 {
66     return([(VLCPlaylist *)[self delegate] menuForEvent: o_event]);
67 }
68
69 - (void)keyDown:(NSEvent *)o_event
70 {
71     unichar key = 0;
72
73     if ([[o_event characters] length])
74         key = [[o_event characters] characterAtIndex: 0];
75
76     switch(key) {
77         case NSDeleteCharacter:
78         case NSDeleteFunctionKey:
79         case NSDeleteCharFunctionKey:
80         case NSBackspaceCharacter:
81             [(VLCPlaylist *)[self delegate] deleteItem:self];
82             break;
83
84         case NSEnterCharacter:
85         case NSCarriageReturnCharacter:
86             [(VLCPlaylist *)[[VLCMain sharedInstance] playlist] playItem:nil];
87             break;
88
89         default:
90             [super keyDown: o_event];
91             break;
92     }
93 }
94
95 - (BOOL)validateMenuItem:(NSMenuItem *)item
96 {
97     if (([self numberOfSelectedRows] >= 1 && [item action] == @selector(delete:)) || [item action] == @selector(selectAll:))
98         return YES;
99
100     return NO;
101 }
102
103 - (BOOL)acceptsFirstResponder
104 {
105     return YES;
106 }
107
108 - (BOOL)becomeFirstResponder
109 {
110     [self setNeedsDisplay:YES];
111     return YES;
112 }
113
114 - (BOOL)resignFirstResponder
115 {
116     [self setNeedsDisplay:YES];
117     return YES;
118 }
119
120 - (IBAction)delete:(id)sender
121 {
122     [[[VLCMain sharedInstance] playlist] deleteItem: sender];
123 }
124
125 @end
126
127 /*****************************************************************************
128  * VLCPlaylistCommon implementation
129  *
130  * This class the superclass of the VLCPlaylist and VLCPlaylistWizard.
131  * It contains the common methods and elements of these 2 entities.
132  *****************************************************************************/
133 @interface VLCPlaylistCommon ()
134 {
135     playlist_item_t * p_current_root_item;
136 }
137 @end
138
139 @implementation VLCPlaylistCommon
140
141 - (id)init
142 {
143     playlist_t * p_playlist = pl_Get(VLCIntf);
144     p_current_root_item = p_playlist->p_local_category;
145
146     self = [super init];
147     if (self != nil)
148         o_outline_dict = [[NSMutableDictionary alloc] init];
149
150     return self;
151 }
152
153 - (void)awakeFromNib
154 {
155     playlist_t * p_playlist = pl_Get(VLCIntf);
156     [o_outline_view setTarget: self];
157     [o_outline_view setDelegate: self];
158     [o_outline_view setDataSource: self];
159     [o_outline_view setAllowsEmptySelection: NO];
160     [o_outline_view expandItem: [o_outline_view itemAtRow:0]];
161
162     [o_outline_view_other setTarget: self];
163     [o_outline_view_other setDelegate: self];
164     [o_outline_view_other setDataSource: self];
165     [o_outline_view_other setAllowsEmptySelection: NO];
166
167     [[o_tc_name_other headerCell] setStringValue:_NS("Name")];
168     [[o_tc_author_other headerCell] setStringValue:_NS("Author")];
169     [[o_tc_duration_other headerCell] setStringValue:_NS("Duration")];
170
171     [self reloadStyles];
172 }
173
174 - (void)setPlaylistRoot: (playlist_item_t *)root_item
175 {
176     p_current_root_item = root_item;
177     [o_outline_view reloadData];
178     [o_outline_view_other reloadData];
179 }
180
181 - (playlist_item_t *)currentPlaylistRoot
182 {
183     return p_current_root_item;
184 }
185
186 - (NSOutlineView *)outlineView
187 {
188     return o_outline_view;
189 }
190
191 - (playlist_item_t *)selectedPlaylistItem
192 {
193     return [[o_outline_view itemAtRow: [o_outline_view selectedRow]]
194                                                                 pointerValue];
195 }
196
197 - (void)reloadStyles
198 {
199     NSFont *fontToUse;
200     CGFloat rowHeight;
201     if (config_GetInt(VLCIntf, "macosx-large-text")) {
202         fontToUse = [NSFont systemFontOfSize:13.];
203         rowHeight = 21.;
204     } else {
205         fontToUse = [NSFont systemFontOfSize:11.];
206         rowHeight = 16.;
207     }
208
209     NSArray *columns = [o_outline_view tableColumns];
210     NSUInteger count = columns.count;
211     for (NSUInteger x = 0; x < count; x++)
212         [[[columns objectAtIndex:x] dataCell] setFont:fontToUse];
213     [o_outline_view setRowHeight:rowHeight];
214
215     columns = [o_outline_view_other tableColumns];
216     count = columns.count;
217     for (NSUInteger x = 0; x < count; x++)
218         [[[columns objectAtIndex:x] dataCell] setFont:fontToUse];
219     [o_outline_view_other setRowHeight:rowHeight];
220 }
221
222 - (void)dealloc {
223     [o_outline_dict release];
224     [super dealloc];
225 }
226
227 @end
228
229 @implementation VLCPlaylistCommon (NSOutlineViewDataSource)
230 /* return the number of children for Obj-C pointer item */ /* DONE */
231 - (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
232 {
233     int i_return = 0;
234     playlist_item_t *p_item = NULL;
235     playlist_t * p_playlist = pl_Get(VLCIntf);
236     //assert(outlineView == o_outline_view);
237
238     PL_LOCK;
239     if (!item)
240         p_item = p_current_root_item;
241     else
242         p_item = (playlist_item_t *)[item pointerValue];
243
244     if (p_item)
245         i_return = p_item->i_children;
246     PL_UNLOCK;
247
248     return i_return > 0 ? i_return : 0;
249 }
250
251 /* return the child at index for the Obj-C pointer item */ /* DONE */
252 - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item
253 {
254     playlist_item_t *p_return = NULL, *p_item = NULL;
255     NSValue *o_value;
256     playlist_t * p_playlist = pl_Get(VLCIntf);
257
258     PL_LOCK;
259     if (item == nil)
260         p_item = p_current_root_item; /* root object */
261     else
262         p_item = (playlist_item_t *)[item pointerValue];
263
264     if (p_item && index < p_item->i_children && index >= 0)
265         p_return = p_item->pp_children[index];
266     PL_UNLOCK;
267
268     o_value = [o_outline_dict objectForKey:[NSString stringWithFormat: @"%p", p_return]];
269
270     if (o_value == nil) {
271         /* FIXME: Why is there a warning if that happens all the time and seems
272          * to be normal? Add an assert and fix it.
273          * msg_Warn(VLCIntf, "playlist item misses pointer value, adding one"); */
274         o_value = [[NSValue valueWithPointer: p_return] retain];
275     }
276     return o_value;
277 }
278
279 /* is the item expandable */
280 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
281 {
282     int i_return = 0;
283     playlist_t *p_playlist = pl_Get(VLCIntf);
284
285     PL_LOCK;
286     if (item == nil) {
287         /* root object */
288         if (p_current_root_item) {
289             i_return = p_current_root_item->i_children;
290         }
291     } else {
292         playlist_item_t *p_item = (playlist_item_t *)[item pointerValue];
293         if (p_item)
294             i_return = p_item->i_children;
295     }
296     PL_UNLOCK;
297
298     return (i_return >= 0);
299 }
300
301 /* retrieve the string values for the cells */
302 - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)o_tc byItem:(id)item
303 {
304     id o_value = nil;
305     char * psz_value;
306     playlist_item_t *p_item;
307
308     /* For error handling */
309     static BOOL attempted_reload = NO;
310
311     if (item == nil || ![item isKindOfClass: [NSValue class]]) {
312         /* Attempt to fix the error by asking for a data redisplay
313          * This might cause infinite loop, so add a small check */
314         if (!attempted_reload) {
315             attempted_reload = YES;
316             [outlineView reloadData];
317         }
318         return @"error" ;
319     }
320
321     p_item = (playlist_item_t *)[item pointerValue];
322     if (!p_item || !p_item->p_input) {
323         /* Attempt to fix the error by asking for a data redisplay
324          * This might cause infinite loop, so add a small check */
325         if (!attempted_reload) {
326             attempted_reload = YES;
327             [outlineView reloadData];
328         }
329         return @"error";
330     }
331
332     attempted_reload = NO;
333     NSString * o_identifier = [o_tc identifier];
334
335     if ([o_identifier isEqualToString:TRACKNUM_COLUMN]) {
336         psz_value = input_item_GetTrackNumber(p_item->p_input);
337         if (psz_value) {
338             o_value = [NSString stringWithUTF8String:psz_value];
339             free(psz_value);
340         }
341     } else if ([o_identifier isEqualToString:TITLE_COLUMN]) {
342         /* sanity check to prevent the NSString class from crashing */
343         char *psz_title =  input_item_GetTitleFbName(p_item->p_input);
344         if (psz_title) {
345             o_value = [NSString stringWithUTF8String:psz_title];
346             free(psz_title);
347         }
348     } else if ([o_identifier isEqualToString:ARTIST_COLUMN]) {
349         psz_value = input_item_GetArtist(p_item->p_input);
350         if (psz_value) {
351             o_value = [NSString stringWithUTF8String:psz_value];
352             free(psz_value);
353         }
354     } else if ([o_identifier isEqualToString:@"duration"]) {
355         char psz_duration[MSTRTIME_MAX_SIZE];
356         mtime_t dur = input_item_GetDuration(p_item->p_input);
357         if (dur != -1) {
358             secstotimestr(psz_duration, dur/1000000);
359             o_value = [NSString stringWithUTF8String:psz_duration];
360         }
361         else
362             o_value = @"--:--";
363     } else if ([o_identifier isEqualToString:GENRE_COLUMN]) {
364         psz_value = input_item_GetGenre(p_item->p_input);
365         if (psz_value) {
366             o_value = [NSString stringWithUTF8String:psz_value];
367             free(psz_value);
368         }
369     } else if ([o_identifier isEqualToString:ALBUM_COLUMN]) {
370         psz_value = input_item_GetAlbum(p_item->p_input);
371         if (psz_value) {
372             o_value = [NSString stringWithUTF8String:psz_value];
373             free(psz_value);
374         }
375     } else if ([o_identifier isEqualToString:DESCRIPTION_COLUMN]) {
376         psz_value = input_item_GetDescription(p_item->p_input);
377         if (psz_value) {
378             o_value = [NSString stringWithUTF8String:psz_value];
379             free(psz_value);
380         }
381     } else if ([o_identifier isEqualToString:DATE_COLUMN]) {
382         psz_value = input_item_GetDate(p_item->p_input);
383         if (psz_value) {
384             o_value = [NSString stringWithUTF8String:psz_value];
385             free(psz_value);
386         }
387     } else if ([o_identifier isEqualToString:LANGUAGE_COLUMN]) {
388         psz_value = input_item_GetLanguage(p_item->p_input);
389         if (psz_value) {
390             o_value = [NSString stringWithUTF8String:psz_value];
391             free(psz_value);
392         }
393     }
394     else if ([o_identifier isEqualToString:URI_COLUMN]) {
395         psz_value = decode_URI(input_item_GetURI(p_item->p_input));
396         if (psz_value) {
397             o_value = [NSString stringWithUTF8String:psz_value];
398             free(psz_value);
399         }
400     }
401     else if ([o_identifier isEqualToString:FILESIZE_COLUMN]) {
402         psz_value = input_item_GetURI(p_item->p_input);
403         o_value = @"";
404         if (psz_value) {
405             NSURL *url = [NSURL URLWithString:[NSString stringWithUTF8String:psz_value]];
406             if ([url isFileURL]) {
407                 NSFileManager *fileManager = [NSFileManager defaultManager];
408                 if ([fileManager fileExistsAtPath:[url path]]) {
409                     NSError *error;
410                     NSDictionary *attributes = [fileManager attributesOfItemAtPath:[url path] error:&error];
411                     o_value = [VLCByteCountFormatter stringFromByteCount:[attributes fileSize] countStyle:NSByteCountFormatterCountStyleDecimal];
412                 }
413             }
414             free(psz_value);
415         }
416     }
417     else if ([o_identifier isEqualToString:@"status"]) {
418         if (input_item_HasErrorWhenReading(p_item->p_input)) {
419             o_value = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kAlertCautionIcon)];
420             [o_value setSize: NSMakeSize(16,16)];
421         }
422     }
423
424     return o_value;
425 }
426
427 @end
428
429 /*****************************************************************************
430  * VLCPlaylistWizard implementation
431  *****************************************************************************/
432 @implementation VLCPlaylistWizard
433
434 - (IBAction)reloadOutlineView
435 {
436     /* Only reload the outlineview if the wizard window is open since this can
437        be quite long on big playlists */
438     if ([[o_outline_view window] isVisible])
439         [o_outline_view reloadData];
440 }
441
442 @end
443
444 /*****************************************************************************
445  * An extension to NSOutlineView's interface to fix compilation warnings
446  * and let us access these 2 functions properly.
447  * This uses a private API, but works fine on all current OSX releases.
448  * Radar ID 11739459 request a public API for this. However, it is probably
449  * easier and faster to recreate similar looking bitmaps ourselves.
450  *****************************************************************************/
451
452 @interface NSOutlineView (UndocumentedSortImages)
453 + (NSImage *)_defaultTableHeaderSortImage;
454 + (NSImage *)_defaultTableHeaderReverseSortImage;
455 @end
456
457
458 /*****************************************************************************
459  * VLCPlaylist implementation
460  *****************************************************************************/
461 @interface VLCPlaylist ()
462 {
463     NSImage *o_descendingSortingImage;
464     NSImage *o_ascendingSortingImage;
465
466     NSMutableArray *o_nodes_array;
467     NSMutableArray *o_items_array;
468
469     BOOL b_selected_item_met;
470     BOOL b_isSortDescending;
471     id o_tc_sortColumn;
472     NSUInteger retainedRowSelection;
473
474     BOOL b_playlistmenu_nib_loaded;
475     BOOL b_view_setup;
476 }
477
478 - (void)saveTableColumns;
479 @end
480
481 @implementation VLCPlaylist
482
483 + (void)initialize
484 {
485     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
486     NSMutableArray *o_columnArray = [[NSMutableArray alloc] init];
487     [o_columnArray addObject: [NSArray arrayWithObjects:TITLE_COLUMN, [NSNumber numberWithFloat:190.], nil]];
488     [o_columnArray addObject: [NSArray arrayWithObjects:ARTIST_COLUMN, [NSNumber numberWithFloat:95.], nil]];
489     [o_columnArray addObject: [NSArray arrayWithObjects:DURATION_COLUMN, [NSNumber numberWithFloat:95.], nil]];
490
491     NSDictionary *appDefaults = [NSDictionary dictionaryWithObjectsAndKeys:
492                                  [NSArray arrayWithArray:o_columnArray], @"PlaylistColumnSelection",
493                                  [NSArray array], @"recentlyPlayedMediaList",
494                                  [NSDictionary dictionary], @"recentlyPlayedMedia", nil];
495
496     [defaults registerDefaults:appDefaults];
497     [o_columnArray release];
498 }
499
500 - (id)init
501 {
502     self = [super init];
503     if (self != nil) {
504         o_nodes_array = [[NSMutableArray alloc] init];
505         o_items_array = [[NSMutableArray alloc] init];
506     }
507     return self;
508 }
509
510 - (void)dealloc
511 {
512     [o_nodes_array release];
513     [o_items_array release];
514     [super dealloc];
515 }
516
517 - (void)awakeFromNib
518 {
519     if (b_view_setup)
520         return;
521
522     playlist_t * p_playlist = pl_Get(VLCIntf);
523
524     [super awakeFromNib];
525     [self initStrings];
526
527     [o_outline_view setDoubleAction: @selector(playItem:)];
528     [o_outline_view_other setDoubleAction: @selector(playItem:)];
529
530     [o_outline_view registerForDraggedTypes: [NSArray arrayWithObjects:NSFilenamesPboardType, @"VLCPlaylistItemPboardType", nil]];
531     [o_outline_view setIntercellSpacing: NSMakeSize (0.0, 1.0)];
532
533     [o_outline_view_other registerForDraggedTypes: [NSArray arrayWithObjects:NSFilenamesPboardType, @"VLCPlaylistItemPboardType", nil]];
534     [o_outline_view_other setIntercellSpacing: NSMakeSize (0.0, 1.0)];
535
536     /* This uses a private API, but works fine on all current OSX releases.
537      * Radar ID 11739459 request a public API for this. However, it is probably
538      * easier and faster to recreate similar looking bitmaps ourselves. */
539     o_ascendingSortingImage = [[NSOutlineView class] _defaultTableHeaderSortImage];
540     o_descendingSortingImage = [[NSOutlineView class] _defaultTableHeaderReverseSortImage];
541
542     o_tc_sortColumn = nil;
543
544     NSArray * o_columnArray = [[NSUserDefaults standardUserDefaults] arrayForKey:@"PlaylistColumnSelection"];
545     NSUInteger count = [o_columnArray count];
546
547     id o_menu = [[VLCMain sharedInstance] mainMenu];
548     NSString * o_column;
549
550     NSMenu *o_context_menu = [o_menu setupPlaylistTableColumnsMenu];
551     [o_playlist_header setMenu: o_context_menu];
552
553     for (NSUInteger i = 0; i < count; i++) {
554         o_column = [[o_columnArray objectAtIndex:i] objectAtIndex:0];
555         if ([o_column isEqualToString:@"status"])
556             continue;
557
558         if(![o_menu setPlaylistColumnTableState: NSOnState forColumn: o_column])
559             continue;
560
561         [[o_outline_view tableColumnWithIdentifier: o_column] setWidth: [[[o_columnArray objectAtIndex:i] objectAtIndex:1] floatValue]];
562     }
563
564     [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(applicationWillTerminate:) name: NSApplicationWillTerminateNotification object: nil];
565
566     b_view_setup = YES;
567 }
568
569 - (void)applicationWillTerminate:(NSNotification *)notification
570 {
571     /* let's make sure we save the correct widths and positions, since this likely changed since the last time the user played with the column selection */
572     [self saveTableColumns];
573 }
574
575 - (void)searchfieldChanged:(NSNotification *)o_notification
576 {
577     [o_search_field setStringValue:[[o_notification object] stringValue]];
578 }
579
580 - (void)initStrings
581 {
582     [o_mi_play setTitle: _NS("Play")];
583     [o_mi_delete setTitle: _NS("Delete")];
584     [o_mi_recursive_expand setTitle: _NS("Expand Node")];
585     [o_mi_selectall setTitle: _NS("Select All")];
586     [o_mi_info setTitle: _NS("Media Information...")];
587     [o_mi_dl_cover_art setTitle: _NS("Download Cover Art")];
588     [o_mi_preparse setTitle: _NS("Fetch Meta Data")];
589     [o_mi_revealInFinder setTitle: _NS("Reveal in Finder")];
590     [o_mm_mi_revealInFinder setTitle: _NS("Reveal in Finder")];
591     [[o_mm_mi_revealInFinder menu] setAutoenablesItems: NO];
592     [o_mi_sort_name setTitle: _NS("Sort Node by Name")];
593     [o_mi_sort_author setTitle: _NS("Sort Node by Author")];
594
595     [o_search_field setToolTip: _NS("Search in Playlist")];
596 }
597
598 - (void)playlistUpdated
599 {
600     /* Clear indications of any existing column sorting */
601     NSUInteger count = [[o_outline_view tableColumns] count];
602     for (NSUInteger i = 0 ; i < count ; i++)
603         [o_outline_view setIndicatorImage:nil inTableColumn: [[o_outline_view tableColumns] objectAtIndex:i]];
604
605     [o_outline_view setHighlightedTableColumn:nil];
606     o_tc_sortColumn = nil;
607     // TODO Find a way to keep the dict size to a minimum
608     //[o_outline_dict removeAllObjects];
609     [o_outline_view reloadData];
610     [[[[VLCMain sharedInstance] wizard] playlistWizard] reloadOutlineView];
611
612     [o_outline_view selectRowIndexes:[NSIndexSet indexSetWithIndex:retainedRowSelection] byExtendingSelection:NO];
613
614     [self outlineViewSelectionDidChange: nil];
615     [[VLCMain sharedInstance] updateMainWindow];
616 }
617
618 - (void)outlineViewSelectionDidChange:(NSNotification *)notification
619 {
620     // FIXME: unsafe
621     playlist_item_t * p_item = [[o_outline_view itemAtRow:[o_outline_view selectedRow]] pointerValue];
622
623     if (p_item) {
624         /* update the state of our Reveal-in-Finder menu items */
625         NSMutableString *o_mrl;
626         char *psz_uri = input_item_GetURI(p_item->p_input);
627
628         [o_mi_revealInFinder setEnabled: NO];
629         [o_mm_mi_revealInFinder setEnabled: NO];
630         if (psz_uri) {
631             o_mrl = [NSMutableString stringWithUTF8String: psz_uri];
632
633             /* perform some checks whether it is a file and if it is local at all... */
634             NSRange prefix_range = [o_mrl rangeOfString: @"file:"];
635             if (prefix_range.location != NSNotFound)
636                 [o_mrl deleteCharactersInRange: prefix_range];
637
638             if ([o_mrl characterAtIndex:0] == '/') {
639                 [o_mi_revealInFinder setEnabled: YES];
640                 [o_mm_mi_revealInFinder setEnabled: YES];
641             }
642             free(psz_uri);
643         }
644
645         /* update our info-panel to reflect the new item */
646         [[[VLCMain sharedInstance] info] updatePanelWithItem:p_item->p_input];
647     }
648 }
649
650 - (BOOL)isSelectionEmpty
651 {
652     return [o_outline_view selectedRow] == -1;
653 }
654
655 - (void)updateRowSelection
656 {
657     // FIXME: unsafe
658     playlist_t *p_playlist = pl_Get(VLCIntf);
659     playlist_item_t *p_item, *p_temp_item;
660     NSMutableArray *o_array = [NSMutableArray array];
661
662     PL_LOCK;
663     p_item = playlist_CurrentPlayingItem(p_playlist);
664     if (p_item == NULL) {
665         PL_UNLOCK;
666         return;
667     }
668
669     p_temp_item = p_item;
670     while(p_temp_item->p_parent) {
671         [o_array insertObject: [NSValue valueWithPointer: p_temp_item] atIndex: 0];
672         p_temp_item = p_temp_item->p_parent;
673     }
674     PL_UNLOCK;
675
676     NSUInteger count = [o_array count];
677     for (NSUInteger j = 0; j < count - 1; j++) {
678         id o_item;
679         if ((o_item = [o_outline_dict objectForKey:
680                             [NSString stringWithFormat: @"%p",
681                             [[o_array objectAtIndex:j] pointerValue]]]) != nil) {
682             [o_outline_view expandItem: o_item];
683         }
684     }
685
686     id o_item = [o_outline_dict objectForKey:[NSString stringWithFormat: @"%p", p_item]];
687     NSInteger i_index = [o_outline_view rowForItem:o_item];
688     [o_outline_view selectRowIndexes:[NSIndexSet indexSetWithIndex:i_index] byExtendingSelection:NO];
689     [o_outline_view setNeedsDisplay:YES];
690 }
691
692 /* Check if p_item is a child of p_node recursively. We need to check the item
693    existence first since OSX sometimes tries to redraw items that have been
694    deleted. We don't do it when not required since this verification takes
695    quite a long time on big playlists (yes, pretty hacky). */
696
697 - (BOOL)isItem: (playlist_item_t *)p_item inNode: (playlist_item_t *)p_node checkItemExistence:(BOOL)b_check locked:(BOOL)b_locked
698 {
699     playlist_t * p_playlist = pl_Get(VLCIntf);
700     playlist_item_t *p_temp_item = p_item;
701
702     if (!p_node)
703         return NO;
704
705     if (p_node == p_item)
706         return YES;
707
708     if (p_node->i_children < 1)
709         return NO;
710
711     if (p_temp_item) {
712         int i;
713         if (!b_locked) PL_LOCK;
714
715         if (b_check) {
716         /* Since outlineView: willDisplayCell:... may call this function with
717            p_items that don't exist anymore, first check if the item is still
718            in the playlist. Any cleaner solution welcomed. */
719             for (i = 0; i < p_playlist->all_items.i_size; i++) {
720                 if (ARRAY_VAL(p_playlist->all_items, i) == p_item)
721                     break;
722                 else if (i == p_playlist->all_items.i_size - 1)
723                 {
724                     if (!b_locked) PL_UNLOCK;
725                     return NO;
726                 }
727             }
728         }
729
730         while(p_temp_item) {
731             p_temp_item = p_temp_item->p_parent;
732             if (p_temp_item == p_node) {
733                 if (!b_locked) PL_UNLOCK;
734                 return YES;
735             }
736         }
737         if (!b_locked) PL_UNLOCK;
738     }
739     return NO;
740 }
741
742 /* This method is useful for instance to remove the selected children of an
743    already selected node */
744 - (void)removeItemsFrom:(id)o_items ifChildrenOf:(id)o_nodes
745 {
746     NSUInteger itemCount = [o_items count];
747     NSUInteger nodeCount = [o_nodes count];
748     for (NSUInteger i = 0 ; i < itemCount ; i++) {
749         for (NSUInteger j = 0 ; j < nodeCount ; j++) {
750             if (o_items == o_nodes) {
751                 if (j == i) continue;
752             }
753             if ([self isItem: [[o_items objectAtIndex:i] pointerValue]
754                     inNode: [[o_nodes objectAtIndex:j] pointerValue]
755                     checkItemExistence: NO locked:NO]) {
756                 [o_items removeObjectAtIndex:i];
757                 /* We need to execute the next iteration with the same index
758                    since the current item has been deleted */
759                 i--;
760                 break;
761             }
762         }
763     }
764 }
765
766 - (IBAction)savePlaylist:(id)sender
767 {
768     playlist_t * p_playlist = pl_Get(VLCIntf);
769
770     NSSavePanel *o_save_panel = [NSSavePanel savePanel];
771     NSString * o_name = [NSString stringWithFormat: @"%@", _NS("Untitled")];
772
773     [NSBundle loadNibNamed:@"PlaylistAccessoryView" owner:self];
774
775     [o_save_accessory_text setStringValue: _NS("File Format:")];
776     [[o_save_accessory_popup itemAtIndex:0] setTitle: _NS("Extended M3U")];
777     [[o_save_accessory_popup itemAtIndex:1] setTitle: _NS("XML Shareable Playlist Format (XSPF)")];
778     [[o_save_accessory_popup itemAtIndex:2] setTitle: _NS("HTML playlist")];
779
780     [o_save_panel setTitle: _NS("Save Playlist")];
781     [o_save_panel setPrompt: _NS("Save")];
782     [o_save_panel setAccessoryView: o_save_accessory_view];
783     [o_save_panel setNameFieldStringValue: o_name];
784
785     if ([o_save_panel runModal] == NSFileHandlingPanelOKButton) {
786         NSString *o_filename = [[o_save_panel URL] path];
787
788         if ([o_save_accessory_popup indexOfSelectedItem] == 0) {
789             NSString * o_real_filename;
790             NSRange range;
791             range.location = [o_filename length] - [@".m3u" length];
792             range.length = [@".m3u" length];
793
794             if ([o_filename compare:@".m3u" options: NSCaseInsensitiveSearch range: range] != NSOrderedSame)
795                 o_real_filename = [NSString stringWithFormat: @"%@.m3u", o_filename];
796             else
797                 o_real_filename = o_filename;
798
799             playlist_Export(p_playlist,
800                 [o_real_filename fileSystemRepresentation],
801                 p_playlist->p_local_category, "export-m3u");
802         } else if ([o_save_accessory_popup indexOfSelectedItem] == 1) {
803             NSString * o_real_filename;
804             NSRange range;
805             range.location = [o_filename length] - [@".xspf" length];
806             range.length = [@".xspf" length];
807
808             if ([o_filename compare:@".xspf" options: NSCaseInsensitiveSearch range: range] != NSOrderedSame)
809                 o_real_filename = [NSString stringWithFormat: @"%@.xspf", o_filename];
810             else
811                 o_real_filename = o_filename;
812
813             playlist_Export(p_playlist,
814                 [o_real_filename fileSystemRepresentation],
815                 p_playlist->p_local_category, "export-xspf");
816         } else {
817             NSString * o_real_filename;
818             NSRange range;
819             range.location = [o_filename length] - [@".html" length];
820             range.length = [@".html" length];
821
822             if ([o_filename compare:@".html" options: NSCaseInsensitiveSearch range: range] != NSOrderedSame)
823                 o_real_filename = [NSString stringWithFormat: @"%@.html", o_filename];
824             else
825                 o_real_filename = o_filename;
826
827             playlist_Export(p_playlist,
828                 [o_real_filename fileSystemRepresentation],
829                 p_playlist->p_local_category, "export-html");
830         }
831     }
832 }
833
834 /* When called retrieves the selected outlineview row and plays that node or item */
835 - (IBAction)playItem:(id)sender
836 {
837     intf_thread_t * p_intf = VLCIntf;
838     playlist_t * p_playlist = pl_Get(p_intf);
839
840     playlist_item_t *p_item;
841     playlist_item_t *p_node = NULL;
842
843     // ignore clicks on column header when handling double action
844     if (sender != nil && [o_outline_view clickedRow] == -1 && sender != o_mi_play)
845         return;
846
847     p_item = [[o_outline_view itemAtRow:[o_outline_view selectedRow]] pointerValue];
848
849     PL_LOCK;
850     if (p_item) {
851         if (p_item->i_children == -1) {
852             p_node = p_item->p_parent;
853         } else {
854             p_node = p_item;
855             if (p_node->i_children > 0 && p_node->pp_children[0]->i_children == -1)
856                 p_item = p_node->pp_children[0];
857             else
858                 p_item = NULL;
859         }
860
861         playlist_Control(p_playlist, PLAYLIST_VIEWPLAY, pl_Locked, p_node, p_item);
862     }
863     PL_UNLOCK;
864 }
865
866 - (IBAction)revealItemInFinder:(id)sender
867 {
868     NSIndexSet * selectedRows = [o_outline_view selectedRowIndexes];
869     NSUInteger count = [selectedRows count];
870     NSUInteger indexes[count];
871     [selectedRows getIndexes:indexes maxCount:count inIndexRange:nil];
872
873     NSMutableString * o_mrl;
874     playlist_item_t *p_item;
875     for (NSUInteger i = 0; i < count; i++) {
876         p_item = [[o_outline_view itemAtRow:indexes[i]] pointerValue];
877
878         if (! p_item || !p_item->p_input)
879             continue;
880
881         char * psz_url = decode_URI(input_item_GetURI(p_item->p_input));
882         o_mrl = [[NSMutableString alloc] initWithString: [NSString stringWithUTF8String:psz_url ? psz_url : ""]];
883         if (psz_url != NULL)
884             free( psz_url );
885
886         /* perform some checks whether it is a file and if it is local at all... */
887         if ([o_mrl length] > 0) {
888             NSRange prefix_range = [o_mrl rangeOfString: @"file:"];
889             if (prefix_range.location != NSNotFound)
890                 [o_mrl deleteCharactersInRange: prefix_range];
891
892             if ([o_mrl characterAtIndex:0] == '/')
893                 [[NSWorkspace sharedWorkspace] selectFile: o_mrl inFileViewerRootedAtPath: o_mrl];
894         }
895
896         [o_mrl release];
897     }
898 }
899
900 /* When called retrieves the selected outlineview row and plays that node or item */
901 - (IBAction)preparseItem:(id)sender
902 {
903     int i_count;
904     NSIndexSet *o_selected_indexes;
905     intf_thread_t * p_intf = VLCIntf;
906     playlist_t * p_playlist = pl_Get(p_intf);
907     playlist_item_t *p_item = NULL;
908
909     o_selected_indexes = [o_outline_view selectedRowIndexes];
910     i_count = [o_selected_indexes count];
911
912     NSUInteger indexes[i_count];
913     [o_selected_indexes getIndexes:indexes maxCount:i_count inIndexRange:nil];
914     for (int i = 0; i < i_count; i++) {
915         p_item = [[o_outline_view itemAtRow:indexes[i]] pointerValue];
916         [o_outline_view deselectRow: indexes[i]];
917
918         if (p_item) {
919             if (p_item->i_children == -1)
920                 libvlc_MetaRequest(p_intf->p_libvlc, p_item->p_input, META_REQUEST_OPTION_NONE);
921             else
922                 msg_Dbg(p_intf, "preparsing nodes not implemented");
923         }
924     }
925     [self playlistUpdated];
926 }
927
928 - (IBAction)downloadCoverArt:(id)sender
929 {
930     int i_count;
931     NSIndexSet *o_selected_indexes;
932     intf_thread_t * p_intf = VLCIntf;
933     playlist_t * p_playlist = pl_Get(p_intf);
934     playlist_item_t *p_item = NULL;
935
936     o_selected_indexes = [o_outline_view selectedRowIndexes];
937     i_count = [o_selected_indexes count];
938
939     NSUInteger indexes[i_count];
940     [o_selected_indexes getIndexes:indexes maxCount:i_count inIndexRange:nil];
941     for (int i = 0; i < i_count; i++) {
942         p_item = [[o_outline_view itemAtRow: indexes[i]] pointerValue];
943
944         if (p_item && p_item->i_children == -1)
945             libvlc_ArtRequest(p_intf->p_libvlc, p_item->p_input, META_REQUEST_OPTION_NONE);
946     }
947     [self playlistUpdated];
948 }
949
950 - (IBAction)selectAll:(id)sender
951 {
952     [o_outline_view selectAll: nil];
953 }
954
955 - (IBAction)showInfoPanel:(id)sender
956 {
957     [[[VLCMain sharedInstance] info] initPanel];
958 }
959
960 - (IBAction)deleteItem:(id)sender
961 {
962     int i_count;
963     NSIndexSet *o_selected_indexes;
964     playlist_t * p_playlist;
965     intf_thread_t * p_intf = VLCIntf;
966
967     o_selected_indexes = [o_outline_view selectedRowIndexes];
968     i_count = [o_selected_indexes count];
969     retainedRowSelection = [o_selected_indexes firstIndex];
970     if (retainedRowSelection == NSNotFound)
971         retainedRowSelection = 0;
972
973     p_playlist = pl_Get(p_intf);
974
975     NSUInteger indexes[i_count];
976     if (i_count == [o_outline_view numberOfRows]) {
977         PL_LOCK;
978         playlist_NodeDelete(p_playlist, [self currentPlaylistRoot], true, false);
979         PL_UNLOCK;
980         [self playlistUpdated];
981         return;
982     }
983     [o_selected_indexes getIndexes:indexes maxCount:i_count inIndexRange:nil];
984     for (int i = 0; i < i_count; i++) {
985         id o_item = [o_outline_view itemAtRow: indexes[i]];
986         [o_outline_view deselectRow: indexes[i]];
987
988         PL_LOCK;
989         playlist_item_t *p_item = [o_item pointerValue];
990         if (!p_item || !p_item->p_input) {
991             PL_UNLOCK;
992             continue;
993         }
994
995         if (p_item->i_children != -1) {
996         //is a node and not an item
997             if (playlist_Status(p_playlist) != PLAYLIST_STOPPED &&
998                 [self isItem: playlist_CurrentPlayingItem(p_playlist) inNode: ((playlist_item_t *)[o_item pointerValue])
999                         checkItemExistence: NO locked:YES] == YES)
1000                 // if current item is in selected node and is playing then stop playlist
1001                 playlist_Control(p_playlist, PLAYLIST_STOP, pl_Locked);
1002
1003                 playlist_NodeDelete(p_playlist, p_item, true, false);
1004         } else
1005             playlist_DeleteFromInput(p_playlist, p_item->p_input, pl_Locked);
1006
1007         PL_UNLOCK;
1008         [o_outline_dict removeObjectForKey:[NSString stringWithFormat:@"%p", [o_item pointerValue]]];
1009         [o_item release];
1010     }
1011
1012     [self playlistUpdated];
1013 }
1014
1015 - (IBAction)sortNodeByName:(id)sender
1016 {
1017     [self sortNode: SORT_TITLE];
1018 }
1019
1020 - (IBAction)sortNodeByAuthor:(id)sender
1021 {
1022     [self sortNode: SORT_ARTIST];
1023 }
1024
1025 - (void)sortNode:(int)i_mode
1026 {
1027     playlist_t * p_playlist = pl_Get(VLCIntf);
1028     playlist_item_t * p_item;
1029
1030     if ([o_outline_view selectedRow] > -1) {
1031         p_item = [[o_outline_view itemAtRow: [o_outline_view selectedRow]] pointerValue];
1032         if (!p_item)
1033             return;
1034     } else
1035         p_item = [self currentPlaylistRoot]; // If no item is selected, sort the whole playlist
1036
1037     PL_LOCK;
1038     if (p_item->i_children > -1) // the item is a node
1039         playlist_RecursiveNodeSort(p_playlist, p_item, i_mode, ORDER_NORMAL);
1040     else
1041         playlist_RecursiveNodeSort(p_playlist, p_item->p_parent, i_mode, ORDER_NORMAL);
1042
1043     PL_UNLOCK;
1044     [self playlistUpdated];
1045 }
1046
1047 - (input_item_t *)createItem:(NSDictionary *)o_one_item
1048 {
1049     intf_thread_t * p_intf = VLCIntf;
1050     playlist_t * p_playlist = pl_Get(p_intf);
1051
1052     input_item_t *p_input;
1053     BOOL b_rem = FALSE, b_dir = FALSE, b_writable = FALSE;
1054     NSString *o_uri, *o_name, *o_path;
1055     NSURL * o_nsurl;
1056     NSArray *o_options;
1057     NSURL *o_true_file;
1058
1059     /* Get the item */
1060     o_uri = (NSString *)[o_one_item objectForKey: @"ITEM_URL"];
1061     o_nsurl = [NSURL URLWithString: o_uri];
1062     o_path = [o_nsurl path];
1063     o_name = (NSString *)[o_one_item objectForKey: @"ITEM_NAME"];
1064     o_options = (NSArray *)[o_one_item objectForKey: @"ITEM_OPTIONS"];
1065
1066     if ([[NSFileManager defaultManager] fileExistsAtPath:o_path isDirectory:&b_dir] && b_dir &&
1067         [[NSWorkspace sharedWorkspace] getFileSystemInfoForPath:o_path isRemovable: &b_rem
1068                                                      isWritable:&b_writable isUnmountable:NULL description:NULL type:NULL] && b_rem && !b_writable && [o_nsurl isFileURL]) {
1069         id o_vlc_open = [[VLCMain sharedInstance] open];
1070
1071         NSString *diskType = [o_vlc_open getVolumeTypeFromMountPath: o_path];
1072         msg_Dbg(p_intf, "detected optical media of type %s in the file input", [diskType UTF8String]);
1073
1074         if ([diskType isEqualToString: kVLCMediaDVD])
1075             o_uri = [NSString stringWithFormat: @"dvdnav://%@", [o_vlc_open getBSDNodeFromMountPath: o_path]];
1076         else if ([diskType isEqualToString: kVLCMediaVideoTSFolder])
1077             o_uri = [NSString stringWithFormat: @"dvdnav://%@", o_path];
1078         else if ([diskType isEqualToString: kVLCMediaAudioCD])
1079             o_uri = [NSString stringWithFormat: @"cdda://%@", [o_vlc_open getBSDNodeFromMountPath: o_path]];
1080         else if ([diskType isEqualToString: kVLCMediaVCD])
1081             o_uri = [NSString stringWithFormat: @"vcd://%@#0:0", [o_vlc_open getBSDNodeFromMountPath: o_path]];
1082         else if ([diskType isEqualToString: kVLCMediaSVCD])
1083             o_uri = [NSString stringWithFormat: @"vcd://%@@0:0", [o_vlc_open getBSDNodeFromMountPath: o_path]];
1084         else if ([diskType isEqualToString: kVLCMediaBD] || [diskType isEqualToString: kVLCMediaBDMVFolder])
1085             o_uri = [NSString stringWithFormat: @"bluray://%@", o_path];
1086         else
1087             msg_Warn(VLCIntf, "unknown disk type, treating %s as regular input", [o_path UTF8String]);
1088
1089         p_input = input_item_New([o_uri UTF8String], [[[NSFileManager defaultManager] displayNameAtPath: o_path] UTF8String]);
1090     }
1091     else
1092         p_input = input_item_New([o_uri fileSystemRepresentation], o_name ? [o_name UTF8String] : NULL);
1093
1094     if (!p_input)
1095         return NULL;
1096
1097     if (o_options) {
1098         NSUInteger count = [o_options count];
1099         for (NSUInteger i = 0; i < count; i++)
1100             input_item_AddOption(p_input, [[o_options objectAtIndex:i] UTF8String], VLC_INPUT_OPTION_TRUSTED);
1101     }
1102
1103     /* Recent documents menu */
1104     if (o_nsurl != nil && (BOOL)config_GetInt(p_playlist, "macosx-recentitems") == YES)
1105         [[NSDocumentController sharedDocumentController] noteNewRecentDocumentURL: o_nsurl];
1106
1107     return p_input;
1108 }
1109
1110 - (void)appendArray:(NSArray*)o_array atPos:(int)i_position enqueue:(BOOL)b_enqueue
1111 {
1112     playlist_t * p_playlist = pl_Get(VLCIntf);
1113     NSUInteger count = [o_array count];
1114     BOOL b_usingPlaylist;
1115     if ([self currentPlaylistRoot] == p_playlist->p_ml_category)
1116         b_usingPlaylist = NO;
1117     else
1118         b_usingPlaylist = YES;
1119
1120     PL_LOCK;
1121     for (NSUInteger i_item = 0; i_item < count; i_item++) {
1122         input_item_t *p_input;
1123         NSDictionary *o_one_item;
1124
1125         /* Get the item */
1126         o_one_item = [o_array objectAtIndex:i_item];
1127         p_input = [self createItem: o_one_item];
1128         if (!p_input)
1129             continue;
1130
1131         /* Add the item */
1132         int returnValue = playlist_AddInput(p_playlist, p_input, PLAYLIST_INSERT, i_position == -1 ? PLAYLIST_END : i_position + i_item, b_usingPlaylist, pl_Locked);
1133         if (returnValue != VLC_SUCCESS) {
1134             vlc_gc_decref(p_input);
1135             continue;
1136         }
1137
1138         if (i_item == 0 && !b_enqueue) {
1139             playlist_item_t *p_item = playlist_ItemGetByInput(p_playlist, p_input);
1140             playlist_Control(p_playlist, PLAYLIST_VIEWPLAY, pl_Locked, p_item->p_parent, p_item);
1141         }
1142
1143         vlc_gc_decref(p_input);
1144     }
1145     PL_UNLOCK;
1146     [self playlistUpdated];
1147 }
1148
1149 - (void)appendNodeArray:(NSArray*)o_array inNode:(playlist_item_t *)p_node atPos:(int)i_position enqueue:(BOOL)b_enqueue
1150 {
1151     playlist_t * p_playlist = pl_Get(VLCIntf);
1152     NSUInteger count = [o_array count];
1153
1154     for (NSUInteger i_item = 0; i_item < count; i_item++) {
1155         input_item_t *p_input;
1156         NSDictionary *o_one_item;
1157
1158         /* Get the item */
1159         PL_LOCK;
1160         o_one_item = [o_array objectAtIndex:i_item];
1161         p_input = [self createItem: o_one_item];
1162
1163         if (!p_input)
1164             continue;
1165
1166         /* Add the item */
1167         playlist_NodeAddInput(p_playlist, p_input, p_node,
1168                                       PLAYLIST_INSERT,
1169                                       i_position == -1 ?
1170                                       PLAYLIST_END : i_position + i_item,
1171                                       pl_Locked);
1172
1173
1174         if (i_item == 0 && !b_enqueue) {
1175             playlist_item_t *p_item;
1176             p_item = playlist_ItemGetByInput(p_playlist, p_input);
1177             playlist_Control(p_playlist, PLAYLIST_VIEWPLAY, pl_Locked, p_node, p_item);
1178         }
1179         PL_UNLOCK;
1180         vlc_gc_decref(p_input);
1181     }
1182     [self playlistUpdated];
1183 }
1184
1185 - (NSMutableArray *)subSearchItem:(playlist_item_t *)p_item
1186 {
1187     playlist_t *p_playlist = pl_Get(VLCIntf);
1188     playlist_item_t *p_selected_item;
1189     int i_selected_row;
1190
1191     i_selected_row = [o_outline_view selectedRow];
1192     if (i_selected_row < 0)
1193         i_selected_row = 0;
1194
1195     p_selected_item = (playlist_item_t *)[[o_outline_view itemAtRow: i_selected_row] pointerValue];
1196
1197     for (NSUInteger i_current = 0; i_current < p_item->i_children ; i_current++) {
1198         char *psz_temp;
1199         NSString *o_current_name, *o_current_author;
1200
1201         PL_LOCK;
1202         o_current_name = [NSString stringWithUTF8String:p_item->pp_children[i_current]->p_input->psz_name];
1203         psz_temp = input_item_GetInfo(p_item->p_input, _("Meta-information"),_("Artist"));
1204         o_current_author = [NSString stringWithUTF8String:psz_temp];
1205         free(psz_temp);
1206         PL_UNLOCK;
1207
1208         if (p_selected_item == p_item->pp_children[i_current] && b_selected_item_met == NO)
1209             b_selected_item_met = YES;
1210         else if (p_selected_item == p_item->pp_children[i_current] && b_selected_item_met == YES)
1211             return NULL;
1212         else if (b_selected_item_met == YES &&
1213                     ([o_current_name rangeOfString:[o_search_field
1214                         stringValue] options:NSCaseInsensitiveSearch].length ||
1215                       [o_current_author rangeOfString:[o_search_field
1216                         stringValue] options:NSCaseInsensitiveSearch].length))
1217             /*Adds the parent items in the result array as well, so that we can
1218             expand the tree*/
1219             return [NSMutableArray arrayWithObject: [NSValue valueWithPointer: p_item->pp_children[i_current]]];
1220
1221         if (p_item->pp_children[i_current]->i_children > 0) {
1222             id o_result = [self subSearchItem:
1223                                             p_item->pp_children[i_current]];
1224             if (o_result != NULL) {
1225                 [o_result insertObject: [NSValue valueWithPointer:
1226                                 p_item->pp_children[i_current]] atIndex:0];
1227                 return o_result;
1228             }
1229         }
1230     }
1231     return NULL;
1232 }
1233
1234 - (IBAction)searchItem:(id)sender
1235 {
1236     playlist_t * p_playlist = pl_Get(VLCIntf);
1237     id o_result;
1238
1239     int i_row = -1;
1240
1241     b_selected_item_met = NO;
1242
1243     /* First, only search after the selected item:
1244      * (b_selected_item_met = NO) */
1245     o_result = [self subSearchItem:[self currentPlaylistRoot]];
1246     if (o_result == NULL)
1247         /* If the first search failed, search again from the beginning */
1248         o_result = [self subSearchItem:[self currentPlaylistRoot]];
1249
1250     if (o_result != NULL) {
1251         int i_start;
1252         if ([[o_result objectAtIndex:0] pointerValue] == p_playlist->p_local_category)
1253             i_start = 1;
1254         else
1255             i_start = 0;
1256         NSUInteger count = [o_result count];
1257
1258         for (NSUInteger i = i_start ; i < count - 1 ; i++) {
1259             [o_outline_view expandItem: [o_outline_dict objectForKey:
1260                         [NSString stringWithFormat: @"%p",
1261                         [[o_result objectAtIndex:i] pointerValue]]]];
1262         }
1263         i_row = [o_outline_view rowForItem: [o_outline_dict objectForKey:
1264                         [NSString stringWithFormat: @"%p",
1265                         [[o_result objectAtIndex:count - 1 ]
1266                         pointerValue]]]];
1267     }
1268     if (i_row > -1) {
1269         [o_outline_view selectRowIndexes:[NSIndexSet indexSetWithIndex:i_row] byExtendingSelection:NO];
1270         [o_outline_view scrollRowToVisible: i_row];
1271     }
1272 }
1273
1274 - (IBAction)recursiveExpandNode:(id)sender
1275 {
1276     NSIndexSet * selectedRows = [o_outline_view selectedRowIndexes];
1277     NSUInteger count = [selectedRows count];
1278     NSUInteger indexes[count];
1279     [selectedRows getIndexes:indexes maxCount:count inIndexRange:nil];
1280
1281     id o_item;
1282     playlist_item_t *p_item;
1283     for (NSUInteger i = 0; i < count; i++) {
1284         o_item = [o_outline_view itemAtRow: indexes[i]];
1285         p_item = (playlist_item_t *)[o_item pointerValue];
1286
1287         if (![[o_outline_view dataSource] outlineView: o_outline_view isItemExpandable: o_item])
1288             o_item = [o_outline_dict objectForKey: [NSString stringWithFormat: @"%p", p_item->p_parent]];
1289
1290         /* We need to collapse the node first, since OSX refuses to recursively
1291          expand an already expanded node, even if children nodes are collapsed. */
1292         [o_outline_view collapseItem: o_item collapseChildren: YES];
1293         [o_outline_view expandItem: o_item expandChildren: YES];
1294
1295         selectedRows = [o_outline_view selectedRowIndexes];
1296         [selectedRows getIndexes:indexes maxCount:count inIndexRange:nil];
1297     }
1298 }
1299
1300 - (NSMenu *)menuForEvent:(NSEvent *)o_event
1301 {
1302     if (!b_playlistmenu_nib_loaded)
1303         b_playlistmenu_nib_loaded = [NSBundle loadNibNamed:@"PlaylistMenu" owner:self];
1304
1305     NSPoint pt;
1306     bool b_rows;
1307     bool b_item_sel;
1308
1309     pt = [o_outline_view convertPoint: [o_event locationInWindow] fromView: nil];
1310     int row = [o_outline_view rowAtPoint:pt];
1311     if (row != -1 && ![[o_outline_view selectedRowIndexes] containsIndex: row])
1312         [o_outline_view selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO];
1313
1314     b_item_sel = (row != -1 && [o_outline_view selectedRow] != -1);
1315     b_rows = [o_outline_view numberOfRows] != 0;
1316
1317     [o_mi_play setEnabled: b_item_sel];
1318     [o_mi_delete setEnabled: b_item_sel];
1319     [o_mi_selectall setEnabled: b_rows];
1320     [o_mi_info setEnabled: b_item_sel];
1321     [o_mi_preparse setEnabled: b_item_sel];
1322     [o_mi_recursive_expand setEnabled: b_item_sel];
1323     [o_mi_sort_name setEnabled: b_item_sel];
1324     [o_mi_sort_author setEnabled: b_item_sel];
1325
1326     return o_ctx_menu;
1327 }
1328
1329 - (void)outlineView: (NSOutlineView *)o_tv didClickTableColumn:(NSTableColumn *)o_tc
1330 {
1331     int i_mode, i_type = 0;
1332     intf_thread_t *p_intf = VLCIntf;
1333     NSString * o_identifier = [o_tc identifier];
1334
1335     playlist_t *p_playlist = pl_Get(p_intf);
1336
1337     if ([o_identifier isEqualToString:TRACKNUM_COLUMN])
1338         i_mode = SORT_TRACK_NUMBER;
1339     else if ([o_identifier isEqualToString:TITLE_COLUMN])
1340         i_mode = SORT_TITLE;
1341     else if ([o_identifier isEqualToString:ARTIST_COLUMN])
1342         i_mode = SORT_ARTIST;
1343     else if ([o_identifier isEqualToString:GENRE_COLUMN])
1344         i_mode = SORT_GENRE;
1345     else if ([o_identifier isEqualToString:DURATION_COLUMN])
1346         i_mode = SORT_DURATION;
1347     else if ([o_identifier isEqualToString:ALBUM_COLUMN])
1348         i_mode = SORT_ALBUM;
1349     else if ([o_identifier isEqualToString:DESCRIPTION_COLUMN])
1350         i_mode = SORT_DESCRIPTION;
1351     else if ([o_identifier isEqualToString:URI_COLUMN])
1352         i_mode = SORT_URI;
1353     else
1354         return;
1355
1356     if (o_tc_sortColumn == o_tc)
1357         b_isSortDescending = !b_isSortDescending;
1358     else
1359         b_isSortDescending = false;
1360
1361     if (b_isSortDescending)
1362         i_type = ORDER_REVERSE;
1363     else
1364         i_type = ORDER_NORMAL;
1365
1366     PL_LOCK;
1367     playlist_RecursiveNodeSort(p_playlist, [self currentPlaylistRoot], i_mode, i_type);
1368     PL_UNLOCK;
1369
1370     [self playlistUpdated];
1371
1372     o_tc_sortColumn = o_tc;
1373     [o_outline_view setHighlightedTableColumn:o_tc];
1374
1375     if (b_isSortDescending)
1376         [o_outline_view setIndicatorImage:o_descendingSortingImage inTableColumn:o_tc];
1377     else
1378         [o_outline_view setIndicatorImage:o_ascendingSortingImage inTableColumn:o_tc];
1379 }
1380
1381
1382 - (void)outlineView:(NSOutlineView *)outlineView
1383                                 willDisplayCell:(id)cell
1384                                 forTableColumn:(NSTableColumn *)tableColumn
1385                                 item:(id)item
1386 {
1387     /* this method can be called when VLC is already dead, hence the extra checks */
1388     intf_thread_t * p_intf = VLCIntf;
1389     if (!p_intf)
1390         return;
1391     playlist_t *p_playlist = pl_Get(p_intf);
1392     if (!p_playlist)
1393         return;
1394
1395     id o_playing_item;
1396
1397     PL_LOCK;
1398     o_playing_item = [o_outline_dict objectForKey: [NSString stringWithFormat:@"%p",  playlist_CurrentPlayingItem(p_playlist)]];
1399     PL_UNLOCK;
1400
1401     NSFont *fontToUse;
1402     if (config_GetInt(VLCIntf, "macosx-large-text"))
1403         fontToUse = [NSFont systemFontOfSize:13.];
1404     else
1405         fontToUse = [NSFont systemFontOfSize:11.];
1406
1407     if ([self isItem: [o_playing_item pointerValue] inNode: [item pointerValue] checkItemExistence:YES locked:NO]
1408                         || [o_playing_item isEqual: item])
1409         [cell setFont: [[NSFontManager sharedFontManager] convertFont:fontToUse toHaveTrait:NSBoldFontMask]];
1410     else
1411         [cell setFont: [[NSFontManager sharedFontManager] convertFont:fontToUse toNotHaveTrait:NSBoldFontMask]];
1412 }
1413
1414 - (id)playingItem
1415 {
1416     playlist_t *p_playlist = pl_Get(VLCIntf);
1417
1418     id o_playing_item;
1419
1420     PL_LOCK;
1421     o_playing_item = [o_outline_dict objectForKey: [NSString stringWithFormat:@"%p",  playlist_CurrentPlayingItem(p_playlist)]];
1422     PL_UNLOCK;
1423
1424     return o_playing_item;
1425 }
1426
1427 - (NSArray *)draggedItems
1428 {
1429     return [[o_nodes_array arrayByAddingObjectsFromArray: o_items_array] retain];
1430 }
1431
1432 - (void)setColumn: (NSString *)o_column state: (NSInteger)i_state translationDict:(NSDictionary *)o_dict
1433 {
1434     NSTableColumn * o_work_tc;
1435
1436     if (i_state == NSOnState) {
1437         NSString *o_title = [o_dict objectForKey:o_column];
1438         if (!o_title)
1439             return;
1440
1441         o_work_tc = [[NSTableColumn alloc] initWithIdentifier: o_column];
1442         [o_work_tc setEditable: NO];
1443         [[o_work_tc dataCell] setFont: [NSFont controlContentFontOfSize:11.]];
1444
1445         [[o_work_tc headerCell] setStringValue: [o_dict objectForKey:o_column]];
1446
1447         if ([o_column isEqualToString: TRACKNUM_COLUMN]) {
1448             [o_work_tc setWidth: 20.];
1449             [o_work_tc setResizingMask: NSTableColumnNoResizing];
1450             [[o_work_tc headerCell] setStringValue: @"#"];
1451         }
1452
1453         [o_outline_view addTableColumn: o_work_tc];
1454         [o_work_tc release];
1455         [o_outline_view reloadData];
1456         [o_outline_view setNeedsDisplay: YES];
1457     }
1458     else
1459         [o_outline_view removeTableColumn: [o_outline_view tableColumnWithIdentifier: o_column]];
1460
1461     [o_outline_view setOutlineTableColumn: [o_outline_view tableColumnWithIdentifier:TITLE_COLUMN]];
1462 }
1463
1464 - (void)saveTableColumns
1465 {
1466     NSMutableArray * o_arrayToSave = [[NSMutableArray alloc] init];
1467     NSArray * o_columns = [[NSArray alloc] initWithArray:[o_outline_view tableColumns]];
1468     NSUInteger count = [o_columns count];
1469     NSTableColumn * o_currentColumn;
1470     for (NSUInteger i = 0; i < count; i++) {
1471         o_currentColumn = [o_columns objectAtIndex:i];
1472         [o_arrayToSave addObject:[NSArray arrayWithObjects:[o_currentColumn identifier], [NSNumber numberWithFloat:[o_currentColumn width]], nil]];
1473     }
1474     [[NSUserDefaults standardUserDefaults] setObject: o_arrayToSave forKey:@"PlaylistColumnSelection"];
1475     [[NSUserDefaults standardUserDefaults] synchronize];
1476     [o_columns release];
1477     [o_arrayToSave release];
1478 }
1479
1480 - (void)continuePlaybackWhereYouLeftOff:(input_thread_t *)p_input_thread
1481 {
1482     NSDictionary *recentlyPlayedFiles = [[NSUserDefaults standardUserDefaults] objectForKey:@"recentlyPlayedMedia"];
1483     if (recentlyPlayedFiles) {
1484         input_item_t *p_item = input_GetItem(p_input_thread);
1485         if (!p_item)
1486             return;
1487
1488         /* allow the user to over-write the start/stop/run-time */
1489         if (var_GetFloat(p_input_thread, "run-time") > 0 ||
1490             var_GetFloat(p_input_thread, "start-time") > 0 ||
1491             var_GetFloat(p_input_thread, "stop-time") > 0) {
1492             return;
1493         }
1494
1495         char *psz_url = decode_URI(input_item_GetURI(p_item));
1496         if (!psz_url)
1497             return;
1498
1499         NSString *url = [NSString stringWithUTF8String:psz_url ? psz_url : ""];
1500         free(psz_url);
1501
1502         /* check for file existance before resuming */
1503         if (![[NSFileManager defaultManager] fileExistsAtPath:[[NSURL URLWithString:[NSString stringWithUTF8String:input_item_GetURI(p_item)]] path]])
1504             return;
1505
1506         NSNumber *lastPosition = [recentlyPlayedFiles objectForKey:url];
1507         if (lastPosition && lastPosition.intValue > 0) {
1508             vlc_value_t pos;
1509             var_Get(p_input_thread, "position", &pos);
1510             float f_current_pos = 100. * pos.f_float;
1511             long long int dur = input_item_GetDuration(p_item) / 1000000;
1512             int current_pos_in_sec = (f_current_pos * dur) / 100;
1513
1514             if (current_pos_in_sec >= lastPosition.intValue)
1515                 return;
1516
1517             int settingValue = config_GetInt(VLCIntf, "macosx-continue-playback");
1518             NSInteger returnValue = NSAlertErrorReturn;
1519
1520             if (settingValue == 0) {
1521                 NSAlert *theAlert = [NSAlert alertWithMessageText:_NS("Continue playback?") defaultButton:_NS("Continue") alternateButton:_NS("Restart playback") otherButton:_NS("Always continue") informativeTextWithFormat:_NS("Playback of \"%@\" will continue at %@"), [NSString stringWithUTF8String:input_item_GetTitleFbName(p_item)], [[VLCStringUtility sharedInstance] stringForTime:lastPosition.intValue]];
1522
1523                 [[VLCCoreInteraction sharedInstance] pause];
1524                 returnValue = [theAlert runModal];
1525                 [[VLCCoreInteraction sharedInstance] playOrPause];
1526             }
1527
1528             if (returnValue == NSAlertAlternateReturn || settingValue == 2)
1529                 lastPosition = [NSNumber numberWithInt:0];
1530
1531             pos.f_float = (float)lastPosition.intValue / (float)dur;
1532             msg_Dbg(VLCIntf, "continuing playback at %2.2f", pos.f_float);
1533             var_Set(p_input_thread, "position", pos);
1534
1535             if (returnValue == NSAlertOtherReturn)
1536                 config_PutInt(VLCIntf, "macosx-continue-playback", 1);
1537         }
1538     }
1539 }
1540
1541 - (void)storePlaybackPositionForItem:(input_thread_t *)p_input_thread
1542 {
1543     input_item_t *p_item = input_GetItem(p_input_thread);
1544     if (!p_item)
1545         return;
1546
1547     char *psz_url = decode_URI(input_item_GetURI(p_item));
1548     NSString *url = [NSString stringWithUTF8String:psz_url ? psz_url : ""];
1549     free(psz_url);
1550
1551     if (url.length < 1)
1552         return;
1553
1554     if ([url rangeOfString:@"file://" options:NSCaseInsensitiveSearch].location != 0)
1555         return;
1556
1557     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
1558     NSMutableDictionary *mutDict = [[NSMutableDictionary alloc] initWithDictionary:[defaults objectForKey:@"recentlyPlayedMedia"]];
1559
1560     vlc_value_t pos;
1561     var_Get(p_input_thread, "position", &pos);
1562     float f_current_pos = 100. * pos.f_float;
1563     long long int dur = input_item_GetDuration(p_item) / 1000000;
1564     int current_pos_in_sec = (f_current_pos * dur) / 100;
1565     NSMutableArray *mediaList = [[defaults objectForKey:@"recentlyPlayedMediaList"] mutableCopy];
1566
1567     if (pos.f_float > .05 && pos.f_float < .95 && dur > 180) {
1568         [mutDict setObject:[NSNumber numberWithInt:current_pos_in_sec] forKey:url];
1569
1570         [mediaList removeObject:url];
1571         [mediaList addObject:url];
1572         NSUInteger mediaListCount = mediaList.count;
1573         if (mediaListCount > 30) {
1574             for (NSUInteger x = 0; x < mediaListCount - 30; x++) {
1575                 [mutDict removeObjectForKey:[mediaList objectAtIndex:0]];
1576                 [mediaList removeObjectAtIndex:0];
1577             }
1578         }
1579     } else {
1580         [mutDict removeObjectForKey:url];
1581         [mediaList removeObject:url];
1582     }
1583     [defaults setObject:mutDict forKey:@"recentlyPlayedMedia"];
1584     [defaults setObject:mediaList forKey:@"recentlyPlayedMediaList"];
1585     [defaults synchronize];
1586
1587     [mutDict release];
1588     [mediaList release];
1589 }
1590
1591 @end
1592
1593 @implementation VLCPlaylist (NSOutlineViewDataSource)
1594
1595 - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item
1596 {
1597     id o_value = [super outlineView: outlineView child: index ofItem: item];
1598
1599     [o_outline_dict setObject:o_value forKey:[NSString stringWithFormat:@"%p", [o_value pointerValue]]];
1600     return o_value;
1601 }
1602
1603 /* Required for drag & drop and reordering */
1604 - (BOOL)outlineView:(NSOutlineView *)outlineView writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pboard
1605 {
1606     playlist_t *p_playlist = pl_Get(VLCIntf);
1607
1608     /* First remove the items that were moved during the last drag & drop
1609        operation */
1610     [o_items_array removeAllObjects];
1611     [o_nodes_array removeAllObjects];
1612
1613     NSUInteger itemCount = [items count];
1614
1615     for (NSUInteger i = 0 ; i < itemCount ; i++) {
1616         id o_item = [items objectAtIndex:i];
1617
1618         /* Fill the items and nodes to move in 2 different arrays */
1619         if (((playlist_item_t *)[o_item pointerValue])->i_children > 0)
1620             [o_nodes_array addObject: o_item];
1621         else
1622             [o_items_array addObject: o_item];
1623     }
1624
1625     /* Now we need to check if there are selected items that are in already
1626        selected nodes. In that case, we only want to move the nodes */
1627     [self removeItemsFrom: o_nodes_array ifChildrenOf: o_nodes_array];
1628     [self removeItemsFrom: o_items_array ifChildrenOf: o_nodes_array];
1629
1630     /* We add the "VLCPlaylistItemPboardType" type to be able to recognize
1631        a Drop operation coming from the playlist. */
1632
1633     [pboard declareTypes: [NSArray arrayWithObject:@"VLCPlaylistItemPboardType"] owner: self];
1634     [pboard setData:[NSData data] forType:@"VLCPlaylistItemPboardType"];
1635
1636     return YES;
1637 }
1638
1639 - (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id <NSDraggingInfo>)info proposedItem:(id)item proposedChildIndex:(NSInteger)index
1640 {
1641     playlist_t *p_playlist = pl_Get(VLCIntf);
1642     NSPasteboard *o_pasteboard = [info draggingPasteboard];
1643
1644     if (!p_playlist) return NSDragOperationNone;
1645
1646     /* Dropping ON items is not allowed if item is not a node */
1647     if (item) {
1648         if (index == NSOutlineViewDropOnItemIndex &&
1649                 ((playlist_item_t *)[item pointerValue])->i_children == -1) {
1650             return NSDragOperationNone;
1651         }
1652     }
1653
1654     /* We refuse to drop an item in anything else than a child of the General
1655        Node. We still accept items that would be root nodes of the outlineview
1656        however, to allow drop in an empty playlist. */
1657     if (!(([self isItem: [item pointerValue] inNode: p_playlist->p_local_category checkItemExistence: NO locked: NO] ||
1658             (var_CreateGetBool(p_playlist, "media-library") && [self isItem: [item pointerValue] inNode: p_playlist->p_ml_category checkItemExistence: NO locked: NO])) || item == nil)) {
1659         return NSDragOperationNone;
1660     }
1661
1662     /* Drop from the Playlist */
1663     if ([[o_pasteboard types] containsObject: @"VLCPlaylistItemPboardType"]) {
1664         NSUInteger count = [o_nodes_array count];
1665         for (NSUInteger i = 0 ; i < count ; i++) {
1666             /* We refuse to Drop in a child of an item we are moving */
1667             if ([self isItem: [item pointerValue] inNode: [[o_nodes_array objectAtIndex:i] pointerValue] checkItemExistence: NO locked:NO]) {
1668                 return NSDragOperationNone;
1669             }
1670         }
1671         return NSDragOperationMove;
1672     }
1673     /* Drop from the Finder */
1674     else if ([[o_pasteboard types] containsObject: NSFilenamesPboardType]) {
1675         return NSDragOperationGeneric;
1676     }
1677     return NSDragOperationNone;
1678 }
1679
1680 - (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id <NSDraggingInfo>)info item:(id)item childIndex:(NSInteger)index
1681 {
1682     playlist_t * p_playlist =  pl_Get(VLCIntf);
1683     NSPasteboard *o_pasteboard = [info draggingPasteboard];
1684
1685     /* Drag & Drop inside the playlist */
1686     if ([[o_pasteboard types] containsObject: @"VLCPlaylistItemPboardType"]) {
1687         if (index == -1) // this is no valid target, sanitize to top of table
1688             index = 0;
1689
1690         int i_row = 0;
1691         playlist_item_t *p_new_parent, *p_item = NULL;
1692         NSArray *o_all_items = [o_nodes_array arrayByAddingObjectsFromArray: o_items_array];
1693         /* If the item is to be dropped as root item of the outline, make it a
1694            child of the respective general node, if is either the pl or the ml
1695            Else, choose the proposed parent as parent. */
1696         if (item == nil) {
1697             if ([self currentPlaylistRoot] == p_playlist->p_local_category || [self currentPlaylistRoot] == p_playlist->p_ml_category)
1698                 p_new_parent = [self currentPlaylistRoot];
1699             else
1700                 return NO;
1701         }
1702         else
1703             p_new_parent = [item pointerValue];
1704
1705         /* Make sure the proposed parent is a node.
1706            (This should never be true) */
1707         if (p_new_parent->i_children < 0)
1708             return NO;
1709
1710         NSUInteger count = [o_all_items count];
1711         if (count == 0)
1712             return NO;
1713
1714         playlist_item_t **pp_items = (playlist_item_t **)calloc(count, sizeof(playlist_item_t*));
1715         if (!pp_items)
1716             return NO;
1717
1718         PL_LOCK;
1719         NSUInteger j = 0;
1720         for (NSUInteger i = 0; i < count; i++) {
1721             p_item = [[o_all_items objectAtIndex:i] pointerValue];
1722             if (p_item)
1723                 pp_items[j++] = p_item;
1724         }
1725
1726         if (j == 0 || playlist_TreeMoveMany(p_playlist, j, pp_items, p_new_parent, index) != VLC_SUCCESS) {
1727             PL_UNLOCK;
1728             free(pp_items);
1729             return NO;
1730         }
1731
1732         PL_UNLOCK;
1733         free(pp_items);
1734
1735         [self playlistUpdated];
1736         i_row = [o_outline_view rowForItem:[o_outline_dict objectForKey:[NSString stringWithFormat: @"%p", [[o_all_items objectAtIndex:0] pointerValue]]]];
1737
1738         if (i_row == -1)
1739             i_row = [o_outline_view rowForItem:[o_outline_dict objectForKey:[NSString stringWithFormat: @"%p", p_new_parent]]];
1740
1741         [o_outline_view deselectAll: self];
1742         [o_outline_view selectRowIndexes:[NSIndexSet indexSetWithIndex:i_row] byExtendingSelection:NO];
1743         [o_outline_view scrollRowToVisible: i_row];
1744
1745         return YES;
1746     }
1747
1748     else if ([[o_pasteboard types] containsObject: NSFilenamesPboardType]) {
1749         if ([self currentPlaylistRoot] != p_playlist->p_local_category && [self currentPlaylistRoot] != p_playlist->p_ml_category)
1750             return NO;
1751
1752         playlist_item_t *p_node = [item pointerValue];
1753
1754         NSArray *o_values = [[o_pasteboard propertyListForType: NSFilenamesPboardType]
1755                                 sortedArrayUsingSelector: @selector(caseInsensitiveCompare:)];
1756         NSUInteger count = [o_values count];
1757         NSMutableArray *o_array = [NSMutableArray arrayWithCapacity:count];
1758         input_thread_t * p_input = pl_CurrentInput(VLCIntf);
1759
1760         if (count == 1 && p_input) {
1761             int i_result = input_AddSubtitleOSD(p_input, vlc_path2uri([[o_values objectAtIndex:0] UTF8String], NULL), true, true);
1762             vlc_object_release(p_input);
1763             if (i_result == VLC_SUCCESS)
1764                 return YES;
1765         }
1766         else if (p_input)
1767             vlc_object_release(p_input);
1768
1769         for (NSUInteger i = 0; i < count; i++) {
1770             NSDictionary *o_dic;
1771             char *psz_uri = vlc_path2uri([[o_values objectAtIndex:i] UTF8String], NULL);
1772             if (!psz_uri)
1773                 continue;
1774
1775             o_dic = [NSDictionary dictionaryWithObject:[NSString stringWithCString:psz_uri encoding:NSUTF8StringEncoding] forKey:@"ITEM_URL"];
1776
1777             free(psz_uri);
1778
1779             [o_array addObject: o_dic];
1780         }
1781
1782         if (item == nil)
1783             [self appendArray:o_array atPos:index enqueue: YES];
1784         else {
1785             assert(p_node->i_children != -1);
1786             [self appendNodeArray:o_array inNode: p_node atPos:index enqueue:YES];
1787         }
1788         return YES;
1789     }
1790     return NO;
1791 }
1792
1793 @end