]> git.sesse.net Git - vlc/blob - modules/gui/macosx/playlist.m
macosx: handle corner case in playlist deletion (refs #11735)
[vlc] / 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[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[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     [[[[VLCMain sharedInstance] bookmarks] dataTable] reloadData];
612
613     [o_outline_view selectRowIndexes:[NSIndexSet indexSetWithIndex:retainedRowSelection] byExtendingSelection:NO];
614
615     [self outlineViewSelectionDidChange: nil];
616     [[VLCMain sharedInstance] updateMainWindow];
617 }
618
619 - (void)outlineViewSelectionDidChange:(NSNotification *)notification
620 {
621     // FIXME: unsafe
622     playlist_item_t * p_item = [[o_outline_view itemAtRow:[o_outline_view selectedRow]] pointerValue];
623
624     if (p_item) {
625         /* update the state of our Reveal-in-Finder menu items */
626         NSMutableString *o_mrl;
627         char *psz_uri = input_item_GetURI(p_item->p_input);
628
629         [o_mi_revealInFinder setEnabled: NO];
630         [o_mm_mi_revealInFinder setEnabled: NO];
631         if (psz_uri) {
632             o_mrl = [NSMutableString stringWithUTF8String: psz_uri];
633
634             /* perform some checks whether it is a file and if it is local at all... */
635             NSRange prefix_range = [o_mrl rangeOfString: @"file:"];
636             if (prefix_range.location != NSNotFound)
637                 [o_mrl deleteCharactersInRange: prefix_range];
638
639             if ([o_mrl characterAtIndex:0] == '/') {
640                 [o_mi_revealInFinder setEnabled: YES];
641                 [o_mm_mi_revealInFinder setEnabled: YES];
642             }
643             free(psz_uri);
644         }
645
646         /* update our info-panel to reflect the new item */
647         [[[VLCMain sharedInstance] info] updatePanelWithItem:p_item->p_input];
648     }
649 }
650
651 - (BOOL)isSelectionEmpty
652 {
653     return [o_outline_view selectedRow] == -1;
654 }
655
656 - (void)updateRowSelection
657 {
658     // FIXME: unsafe
659     playlist_t *p_playlist = pl_Get(VLCIntf);
660     playlist_item_t *p_item, *p_temp_item;
661     NSMutableArray *o_array = [NSMutableArray array];
662
663     PL_LOCK;
664     p_item = playlist_CurrentPlayingItem(p_playlist);
665     if (p_item == NULL) {
666         PL_UNLOCK;
667         return;
668     }
669
670     p_temp_item = p_item;
671     while(p_temp_item->p_parent) {
672         [o_array insertObject: [NSValue valueWithPointer: p_temp_item] atIndex: 0];
673         p_temp_item = p_temp_item->p_parent;
674     }
675     PL_UNLOCK;
676
677     NSUInteger count = [o_array count];
678     for (NSUInteger j = 0; j < count - 1; j++) {
679         id o_item;
680         if ((o_item = [o_outline_dict objectForKey:
681                             [NSString stringWithFormat: @"%p",
682                             [[o_array objectAtIndex:j] pointerValue]]]) != nil) {
683             [o_outline_view expandItem: o_item];
684         }
685     }
686
687     id o_item = [o_outline_dict objectForKey:[NSString stringWithFormat: @"%p", p_item]];
688     NSInteger i_index = [o_outline_view rowForItem:o_item];
689     [o_outline_view selectRowIndexes:[NSIndexSet indexSetWithIndex:i_index] byExtendingSelection:NO];
690     [o_outline_view setNeedsDisplay:YES];
691 }
692
693 /* Check if p_item is a child of p_node recursively. We need to check the item
694    existence first since OSX sometimes tries to redraw items that have been
695    deleted. We don't do it when not required since this verification takes
696    quite a long time on big playlists (yes, pretty hacky). */
697
698 - (BOOL)isItem: (playlist_item_t *)p_item inNode: (playlist_item_t *)p_node checkItemExistence:(BOOL)b_check locked:(BOOL)b_locked
699 {
700     playlist_t * p_playlist = pl_Get(VLCIntf);
701     playlist_item_t *p_temp_item = p_item;
702
703     if (!p_node)
704         return NO;
705
706     if (p_node == p_item)
707         return YES;
708
709     if (p_node->i_children < 1)
710         return NO;
711
712     if (p_temp_item) {
713         int i;
714         if (!b_locked) PL_LOCK;
715
716         if (b_check) {
717         /* Since outlineView: willDisplayCell:... may call this function with
718            p_items that don't exist anymore, first check if the item is still
719            in the playlist. Any cleaner solution welcomed. */
720             for (i = 0; i < p_playlist->all_items.i_size; i++) {
721                 if (ARRAY_VAL(p_playlist->all_items, i) == p_item)
722                     break;
723                 else if (i == p_playlist->all_items.i_size - 1)
724                 {
725                     if (!b_locked) PL_UNLOCK;
726                     return NO;
727                 }
728             }
729         }
730
731         while(p_temp_item) {
732             p_temp_item = p_temp_item->p_parent;
733             if (p_temp_item == p_node) {
734                 if (!b_locked) PL_UNLOCK;
735                 return YES;
736             }
737         }
738         if (!b_locked) PL_UNLOCK;
739     }
740     return NO;
741 }
742
743 /* This method is useful for instance to remove the selected children of an
744    already selected node */
745 - (void)removeItemsFrom:(id)o_items ifChildrenOf:(id)o_nodes
746 {
747     NSUInteger itemCount = [o_items count];
748     NSUInteger nodeCount = [o_nodes count];
749     for (NSUInteger i = 0 ; i < itemCount ; i++) {
750         for (NSUInteger j = 0 ; j < nodeCount ; j++) {
751             if (o_items == o_nodes) {
752                 if (j == i) continue;
753             }
754             if ([self isItem: [[o_items objectAtIndex:i] pointerValue]
755                     inNode: [[o_nodes objectAtIndex:j] pointerValue]
756                     checkItemExistence: NO locked:NO]) {
757                 [o_items removeObjectAtIndex:i];
758                 /* We need to execute the next iteration with the same index
759                    since the current item has been deleted */
760                 i--;
761                 break;
762             }
763         }
764     }
765 }
766
767 - (IBAction)savePlaylist:(id)sender
768 {
769     playlist_t * p_playlist = pl_Get(VLCIntf);
770
771     NSSavePanel *o_save_panel = [NSSavePanel savePanel];
772     NSString * o_name = [NSString stringWithFormat: @"%@", _NS("Untitled")];
773
774     [NSBundle loadNibNamed:@"PlaylistAccessoryView" owner:self];
775
776     [o_save_accessory_text setStringValue: _NS("File Format:")];
777     [[o_save_accessory_popup itemAtIndex:0] setTitle: _NS("Extended M3U")];
778     [[o_save_accessory_popup itemAtIndex:1] setTitle: _NS("XML Shareable Playlist Format (XSPF)")];
779     [[o_save_accessory_popup itemAtIndex:2] setTitle: _NS("HTML playlist")];
780
781     [o_save_panel setTitle: _NS("Save Playlist")];
782     [o_save_panel setPrompt: _NS("Save")];
783     [o_save_panel setAccessoryView: o_save_accessory_view];
784     [o_save_panel setNameFieldStringValue: o_name];
785
786     if ([o_save_panel runModal] == NSFileHandlingPanelOKButton) {
787         NSString *o_filename = [[o_save_panel URL] path];
788
789         if ([o_save_accessory_popup indexOfSelectedItem] == 0) {
790             NSString * o_real_filename;
791             NSRange range;
792             range.location = [o_filename length] - [@".m3u" length];
793             range.length = [@".m3u" length];
794
795             if ([o_filename compare:@".m3u" options: NSCaseInsensitiveSearch range: range] != NSOrderedSame)
796                 o_real_filename = [NSString stringWithFormat: @"%@.m3u", o_filename];
797             else
798                 o_real_filename = o_filename;
799
800             playlist_Export(p_playlist,
801                 [o_real_filename fileSystemRepresentation],
802                 p_playlist->p_local_category, "export-m3u");
803         } else if ([o_save_accessory_popup indexOfSelectedItem] == 1) {
804             NSString * o_real_filename;
805             NSRange range;
806             range.location = [o_filename length] - [@".xspf" length];
807             range.length = [@".xspf" length];
808
809             if ([o_filename compare:@".xspf" options: NSCaseInsensitiveSearch range: range] != NSOrderedSame)
810                 o_real_filename = [NSString stringWithFormat: @"%@.xspf", o_filename];
811             else
812                 o_real_filename = o_filename;
813
814             playlist_Export(p_playlist,
815                 [o_real_filename fileSystemRepresentation],
816                 p_playlist->p_local_category, "export-xspf");
817         } else {
818             NSString * o_real_filename;
819             NSRange range;
820             range.location = [o_filename length] - [@".html" length];
821             range.length = [@".html" length];
822
823             if ([o_filename compare:@".html" options: NSCaseInsensitiveSearch range: range] != NSOrderedSame)
824                 o_real_filename = [NSString stringWithFormat: @"%@.html", o_filename];
825             else
826                 o_real_filename = o_filename;
827
828             playlist_Export(p_playlist,
829                 [o_real_filename fileSystemRepresentation],
830                 p_playlist->p_local_category, "export-html");
831         }
832     }
833 }
834
835 /* When called retrieves the selected outlineview row and plays that node or item */
836 - (IBAction)playItem:(id)sender
837 {
838     intf_thread_t * p_intf = VLCIntf;
839     playlist_t * p_playlist = pl_Get(p_intf);
840
841     playlist_item_t *p_item;
842     playlist_item_t *p_node = NULL;
843
844     // ignore clicks on column header when handling double action
845     if (sender != nil && [o_outline_view clickedRow] == -1 && sender != o_mi_play)
846         return;
847
848     p_item = [[o_outline_view itemAtRow:[o_outline_view selectedRow]] pointerValue];
849
850     PL_LOCK;
851     if (p_item) {
852         if (p_item->i_children == -1) {
853             p_node = p_item->p_parent;
854         } else {
855             p_node = p_item;
856             if (p_node->i_children > 0 && p_node->pp_children[0]->i_children == -1)
857                 p_item = p_node->pp_children[0];
858             else
859                 p_item = NULL;
860         }
861
862         playlist_Control(p_playlist, PLAYLIST_VIEWPLAY, pl_Locked, p_node, p_item);
863     }
864     PL_UNLOCK;
865 }
866
867 - (IBAction)revealItemInFinder:(id)sender
868 {
869     NSIndexSet * selectedRows = [o_outline_view selectedRowIndexes];
870     NSUInteger count = [selectedRows count];
871     NSUInteger indexes[count];
872     [selectedRows getIndexes:indexes maxCount:count inIndexRange:nil];
873
874     NSMutableString * o_mrl;
875     playlist_item_t *p_item;
876     for (NSUInteger i = 0; i < count; i++) {
877         p_item = [[o_outline_view itemAtRow:indexes[i]] pointerValue];
878
879         if (! p_item || !p_item->p_input)
880             continue;
881
882         char * psz_url = decode_URI(input_item_GetURI(p_item->p_input));
883         o_mrl = [[NSMutableString alloc] initWithString: [NSString stringWithUTF8String:psz_url ? psz_url : ""]];
884         if (psz_url != NULL)
885             free( psz_url );
886
887         /* perform some checks whether it is a file and if it is local at all... */
888         if ([o_mrl length] > 0) {
889             NSRange prefix_range = [o_mrl rangeOfString: @"file:"];
890             if (prefix_range.location != NSNotFound)
891                 [o_mrl deleteCharactersInRange: prefix_range];
892
893             if ([o_mrl characterAtIndex:0] == '/')
894                 [[NSWorkspace sharedWorkspace] selectFile: o_mrl inFileViewerRootedAtPath: o_mrl];
895         }
896
897         [o_mrl release];
898     }
899 }
900
901 /* When called retrieves the selected outlineview row and plays that node or item */
902 - (IBAction)preparseItem:(id)sender
903 {
904     int i_count;
905     NSIndexSet *o_selected_indexes;
906     intf_thread_t * p_intf = VLCIntf;
907     playlist_t * p_playlist = pl_Get(p_intf);
908     playlist_item_t *p_item = NULL;
909
910     o_selected_indexes = [o_outline_view selectedRowIndexes];
911     i_count = [o_selected_indexes count];
912
913     NSUInteger indexes[i_count];
914     [o_selected_indexes getIndexes:indexes maxCount:i_count inIndexRange:nil];
915     for (int i = 0; i < i_count; i++) {
916         p_item = [[o_outline_view itemAtRow:indexes[i]] pointerValue];
917         [o_outline_view deselectRow: indexes[i]];
918
919         if (p_item) {
920             if (p_item->i_children == -1)
921                 libvlc_MetaRequest(p_intf->p_libvlc, p_item->p_input, META_REQUEST_OPTION_NONE);
922             else
923                 msg_Dbg(p_intf, "preparsing nodes not implemented");
924         }
925     }
926     [self playlistUpdated];
927 }
928
929 - (IBAction)downloadCoverArt:(id)sender
930 {
931     int i_count;
932     NSIndexSet *o_selected_indexes;
933     intf_thread_t * p_intf = VLCIntf;
934     playlist_t * p_playlist = pl_Get(p_intf);
935     playlist_item_t *p_item = NULL;
936
937     o_selected_indexes = [o_outline_view selectedRowIndexes];
938     i_count = [o_selected_indexes count];
939
940     NSUInteger indexes[i_count];
941     [o_selected_indexes getIndexes:indexes maxCount:i_count inIndexRange:nil];
942     for (int i = 0; i < i_count; i++) {
943         p_item = [[o_outline_view itemAtRow: indexes[i]] pointerValue];
944
945         if (p_item && p_item->i_children == -1)
946             libvlc_ArtRequest(p_intf->p_libvlc, p_item->p_input, META_REQUEST_OPTION_NONE);
947     }
948     [self playlistUpdated];
949 }
950
951 - (IBAction)selectAll:(id)sender
952 {
953     [o_outline_view selectAll: nil];
954 }
955
956 - (IBAction)showInfoPanel:(id)sender
957 {
958     [[[VLCMain sharedInstance] info] initPanel];
959 }
960
961 - (IBAction)deleteItem:(id)sender
962 {
963     int i_count;
964     NSIndexSet *o_selected_indexes;
965     playlist_t * p_playlist;
966     intf_thread_t * p_intf = VLCIntf;
967
968     o_selected_indexes = [o_outline_view selectedRowIndexes];
969     i_count = [o_selected_indexes count];
970     retainedRowSelection = [o_selected_indexes firstIndex];
971     if (retainedRowSelection == NSNotFound)
972         retainedRowSelection = 0;
973
974     p_playlist = pl_Get(p_intf);
975
976     NSUInteger indexes[i_count];
977     if (i_count == [o_outline_view numberOfRows]) {
978         PL_LOCK;
979         playlist_NodeDelete(p_playlist, [self currentPlaylistRoot], true, false);
980         PL_UNLOCK;
981         [self playlistUpdated];
982         return;
983     }
984     [o_selected_indexes getIndexes:indexes maxCount:i_count inIndexRange:nil];
985     for (int i = 0; i < i_count; i++) {
986         id o_item = [o_outline_view itemAtRow: indexes[i]];
987         [o_outline_view deselectRow: indexes[i]];
988
989         PL_LOCK;
990         playlist_item_t *p_item = [o_item pointerValue];
991         if (!p_item || !p_item->p_input) {
992             PL_UNLOCK;
993             continue;
994         }
995
996         if (p_item->i_children != -1) {
997         //is a node and not an item
998             if (playlist_Status(p_playlist) != PLAYLIST_STOPPED &&
999                 [self isItem: playlist_CurrentPlayingItem(p_playlist) inNode: ((playlist_item_t *)[o_item pointerValue])
1000                         checkItemExistence: NO locked:YES] == YES)
1001                 // if current item is in selected node and is playing then stop playlist
1002                 playlist_Control(p_playlist, PLAYLIST_STOP, pl_Locked);
1003
1004                 playlist_NodeDelete(p_playlist, p_item, true, false);
1005         } else
1006             playlist_DeleteFromInput(p_playlist, p_item->p_input, pl_Locked);
1007
1008         PL_UNLOCK;
1009         [o_outline_dict removeObjectForKey:[NSString stringWithFormat:@"%p", [o_item pointerValue]]];
1010         [o_item release];
1011     }
1012
1013     [self playlistUpdated];
1014 }
1015
1016 - (IBAction)sortNodeByName:(id)sender
1017 {
1018     [self sortNode: SORT_TITLE];
1019 }
1020
1021 - (IBAction)sortNodeByAuthor:(id)sender
1022 {
1023     [self sortNode: SORT_ARTIST];
1024 }
1025
1026 - (void)sortNode:(int)i_mode
1027 {
1028     playlist_t * p_playlist = pl_Get(VLCIntf);
1029     playlist_item_t * p_item;
1030
1031     if ([o_outline_view selectedRow] > -1) {
1032         p_item = [[o_outline_view itemAtRow: [o_outline_view selectedRow]] pointerValue];
1033         if (!p_item)
1034             return;
1035     } else
1036         p_item = [self currentPlaylistRoot]; // If no item is selected, sort the whole playlist
1037
1038     PL_LOCK;
1039     if (p_item->i_children > -1) // the item is a node
1040         playlist_RecursiveNodeSort(p_playlist, p_item, i_mode, ORDER_NORMAL);
1041     else
1042         playlist_RecursiveNodeSort(p_playlist, p_item->p_parent, i_mode, ORDER_NORMAL);
1043
1044     PL_UNLOCK;
1045     [self playlistUpdated];
1046 }
1047
1048 - (input_item_t *)createItem:(NSDictionary *)o_one_item
1049 {
1050     intf_thread_t * p_intf = VLCIntf;
1051     playlist_t * p_playlist = pl_Get(p_intf);
1052
1053     input_item_t *p_input;
1054     BOOL b_rem = FALSE, b_dir = FALSE, b_writable = FALSE;
1055     NSString *o_uri, *o_name, *o_path;
1056     NSURL * o_nsurl;
1057     NSArray *o_options;
1058     NSURL *o_true_file;
1059
1060     /* Get the item */
1061     o_uri = (NSString *)[o_one_item objectForKey: @"ITEM_URL"];
1062     o_nsurl = [NSURL URLWithString: o_uri];
1063     o_path = [o_nsurl path];
1064     o_name = (NSString *)[o_one_item objectForKey: @"ITEM_NAME"];
1065     o_options = (NSArray *)[o_one_item objectForKey: @"ITEM_OPTIONS"];
1066
1067     if ([[NSFileManager defaultManager] fileExistsAtPath:o_path isDirectory:&b_dir] && b_dir &&
1068         [[NSWorkspace sharedWorkspace] getFileSystemInfoForPath:o_path isRemovable: &b_rem
1069                                                      isWritable:&b_writable isUnmountable:NULL description:NULL type:NULL] && b_rem && !b_writable && [o_nsurl isFileURL]) {
1070         id o_vlc_open = [[VLCMain sharedInstance] open];
1071
1072         NSString *diskType = [o_vlc_open getVolumeTypeFromMountPath: o_path];
1073         msg_Dbg(p_intf, "detected optical media of type %s in the file input", [diskType UTF8String]);
1074
1075         if ([diskType isEqualToString: kVLCMediaDVD])
1076             o_uri = [NSString stringWithFormat: @"dvdnav://%@", [o_vlc_open getBSDNodeFromMountPath: o_path]];
1077         else if ([diskType isEqualToString: kVLCMediaVideoTSFolder])
1078             o_uri = [NSString stringWithFormat: @"dvdnav://%@", o_path];
1079         else if ([diskType isEqualToString: kVLCMediaAudioCD])
1080             o_uri = [NSString stringWithFormat: @"cdda://%@", [o_vlc_open getBSDNodeFromMountPath: o_path]];
1081         else if ([diskType isEqualToString: kVLCMediaVCD])
1082             o_uri = [NSString stringWithFormat: @"vcd://%@#0:0", [o_vlc_open getBSDNodeFromMountPath: o_path]];
1083         else if ([diskType isEqualToString: kVLCMediaSVCD])
1084             o_uri = [NSString stringWithFormat: @"vcd://%@@0:0", [o_vlc_open getBSDNodeFromMountPath: o_path]];
1085         else if ([diskType isEqualToString: kVLCMediaBD] || [diskType isEqualToString: kVLCMediaBDMVFolder])
1086             o_uri = [NSString stringWithFormat: @"bluray://%@", o_path];
1087         else
1088             msg_Warn(VLCIntf, "unknown disk type, treating %s as regular input", [o_path UTF8String]);
1089
1090         p_input = input_item_New([o_uri UTF8String], [[[NSFileManager defaultManager] displayNameAtPath: o_path] UTF8String]);
1091     }
1092     else
1093         p_input = input_item_New([o_uri fileSystemRepresentation], o_name ? [o_name UTF8String] : NULL);
1094
1095     if (!p_input)
1096         return NULL;
1097
1098     if (o_options) {
1099         NSUInteger count = [o_options count];
1100         for (NSUInteger i = 0; i < count; i++)
1101             input_item_AddOption(p_input, [[o_options objectAtIndex:i] UTF8String], VLC_INPUT_OPTION_TRUSTED);
1102     }
1103
1104     /* Recent documents menu */
1105     if (o_nsurl != nil && (BOOL)config_GetInt(p_playlist, "macosx-recentitems") == YES)
1106         [[NSDocumentController sharedDocumentController] noteNewRecentDocumentURL: o_nsurl];
1107
1108     return p_input;
1109 }
1110
1111 - (void)appendArray:(NSArray*)o_array atPos:(int)i_position enqueue:(BOOL)b_enqueue
1112 {
1113     playlist_t * p_playlist = pl_Get(VLCIntf);
1114     NSUInteger count = [o_array count];
1115     BOOL b_usingPlaylist;
1116     if ([self currentPlaylistRoot] == p_playlist->p_ml_category)
1117         b_usingPlaylist = NO;
1118     else
1119         b_usingPlaylist = YES;
1120
1121     PL_LOCK;
1122     for (NSUInteger i_item = 0; i_item < count; i_item++) {
1123         input_item_t *p_input;
1124         NSDictionary *o_one_item;
1125
1126         /* Get the item */
1127         o_one_item = [o_array objectAtIndex:i_item];
1128         p_input = [self createItem: o_one_item];
1129         if (!p_input)
1130             continue;
1131
1132         /* Add the item */
1133         int returnValue = playlist_AddInput(p_playlist, p_input, PLAYLIST_INSERT, i_position == -1 ? PLAYLIST_END : i_position + i_item, b_usingPlaylist, pl_Locked);
1134         if (returnValue != VLC_SUCCESS) {
1135             vlc_gc_decref(p_input);
1136             continue;
1137         }
1138
1139         if (i_item == 0 && !b_enqueue) {
1140             playlist_item_t *p_item = playlist_ItemGetByInput(p_playlist, p_input);
1141             playlist_Control(p_playlist, PLAYLIST_VIEWPLAY, pl_Locked, p_item->p_parent, p_item);
1142         }
1143
1144         vlc_gc_decref(p_input);
1145     }
1146     PL_UNLOCK;
1147     [self playlistUpdated];
1148 }
1149
1150 - (void)appendNodeArray:(NSArray*)o_array inNode:(playlist_item_t *)p_node atPos:(int)i_position enqueue:(BOOL)b_enqueue
1151 {
1152     playlist_t * p_playlist = pl_Get(VLCIntf);
1153     NSUInteger count = [o_array count];
1154
1155     for (NSUInteger i_item = 0; i_item < count; i_item++) {
1156         input_item_t *p_input;
1157         NSDictionary *o_one_item;
1158
1159         /* Get the item */
1160         PL_LOCK;
1161         o_one_item = [o_array objectAtIndex:i_item];
1162         p_input = [self createItem: o_one_item];
1163
1164         if (!p_input)
1165             continue;
1166
1167         /* Add the item */
1168         playlist_NodeAddInput(p_playlist, p_input, p_node,
1169                                       PLAYLIST_INSERT,
1170                                       i_position == -1 ?
1171                                       PLAYLIST_END : i_position + i_item,
1172                                       pl_Locked);
1173
1174
1175         if (i_item == 0 && !b_enqueue) {
1176             playlist_item_t *p_item;
1177             p_item = playlist_ItemGetByInput(p_playlist, p_input);
1178             playlist_Control(p_playlist, PLAYLIST_VIEWPLAY, pl_Locked, p_node, p_item);
1179         }
1180         PL_UNLOCK;
1181         vlc_gc_decref(p_input);
1182     }
1183     [self playlistUpdated];
1184 }
1185
1186 - (NSMutableArray *)subSearchItem:(playlist_item_t *)p_item
1187 {
1188     playlist_t *p_playlist = pl_Get(VLCIntf);
1189     playlist_item_t *p_selected_item;
1190     int i_selected_row;
1191
1192     i_selected_row = [o_outline_view selectedRow];
1193     if (i_selected_row < 0)
1194         i_selected_row = 0;
1195
1196     p_selected_item = (playlist_item_t *)[[o_outline_view itemAtRow: i_selected_row] pointerValue];
1197
1198     for (NSUInteger i_current = 0; i_current < p_item->i_children ; i_current++) {
1199         char *psz_temp;
1200         NSString *o_current_name, *o_current_author;
1201
1202         PL_LOCK;
1203         o_current_name = [NSString stringWithUTF8String:p_item->pp_children[i_current]->p_input->psz_name];
1204         psz_temp = input_item_GetInfo(p_item->p_input, _("Meta-information"),_("Artist"));
1205         o_current_author = [NSString stringWithUTF8String:psz_temp];
1206         free(psz_temp);
1207         PL_UNLOCK;
1208
1209         if (p_selected_item == p_item->pp_children[i_current] && b_selected_item_met == NO)
1210             b_selected_item_met = YES;
1211         else if (p_selected_item == p_item->pp_children[i_current] && b_selected_item_met == YES)
1212             return NULL;
1213         else if (b_selected_item_met == YES &&
1214                     ([o_current_name rangeOfString:[o_search_field
1215                         stringValue] options:NSCaseInsensitiveSearch].length ||
1216                       [o_current_author rangeOfString:[o_search_field
1217                         stringValue] options:NSCaseInsensitiveSearch].length))
1218             /*Adds the parent items in the result array as well, so that we can
1219             expand the tree*/
1220             return [NSMutableArray arrayWithObject: [NSValue valueWithPointer: p_item->pp_children[i_current]]];
1221
1222         if (p_item->pp_children[i_current]->i_children > 0) {
1223             id o_result = [self subSearchItem:
1224                                             p_item->pp_children[i_current]];
1225             if (o_result != NULL) {
1226                 [o_result insertObject: [NSValue valueWithPointer:
1227                                 p_item->pp_children[i_current]] atIndex:0];
1228                 return o_result;
1229             }
1230         }
1231     }
1232     return NULL;
1233 }
1234
1235 - (IBAction)searchItem:(id)sender
1236 {
1237     playlist_t * p_playlist = pl_Get(VLCIntf);
1238     id o_result;
1239
1240     int i_row = -1;
1241
1242     b_selected_item_met = NO;
1243
1244     /* First, only search after the selected item:
1245      * (b_selected_item_met = NO) */
1246     o_result = [self subSearchItem:[self currentPlaylistRoot]];
1247     if (o_result == NULL)
1248         /* If the first search failed, search again from the beginning */
1249         o_result = [self subSearchItem:[self currentPlaylistRoot]];
1250
1251     if (o_result != NULL) {
1252         int i_start;
1253         if ([[o_result objectAtIndex:0] pointerValue] == p_playlist->p_local_category)
1254             i_start = 1;
1255         else
1256             i_start = 0;
1257         NSUInteger count = [o_result count];
1258
1259         for (NSUInteger i = i_start ; i < count - 1 ; i++) {
1260             [o_outline_view expandItem: [o_outline_dict objectForKey:
1261                         [NSString stringWithFormat: @"%p",
1262                         [[o_result objectAtIndex:i] pointerValue]]]];
1263         }
1264         i_row = [o_outline_view rowForItem: [o_outline_dict objectForKey:
1265                         [NSString stringWithFormat: @"%p",
1266                         [[o_result objectAtIndex:count - 1 ]
1267                         pointerValue]]]];
1268     }
1269     if (i_row > -1) {
1270         [o_outline_view selectRowIndexes:[NSIndexSet indexSetWithIndex:i_row] byExtendingSelection:NO];
1271         [o_outline_view scrollRowToVisible: i_row];
1272     }
1273 }
1274
1275 - (IBAction)recursiveExpandNode:(id)sender
1276 {
1277     NSIndexSet * selectedRows = [o_outline_view selectedRowIndexes];
1278     NSUInteger count = [selectedRows count];
1279     NSUInteger indexes[count];
1280     [selectedRows getIndexes:indexes maxCount:count inIndexRange:nil];
1281
1282     id o_item;
1283     playlist_item_t *p_item;
1284     for (NSUInteger i = 0; i < count; i++) {
1285         o_item = [o_outline_view itemAtRow: indexes[i]];
1286         p_item = (playlist_item_t *)[o_item pointerValue];
1287
1288         if (![[o_outline_view dataSource] outlineView: o_outline_view isItemExpandable: o_item])
1289             o_item = [o_outline_dict objectForKey: [NSString stringWithFormat: @"%p", p_item->p_parent]];
1290
1291         /* We need to collapse the node first, since OSX refuses to recursively
1292          expand an already expanded node, even if children nodes are collapsed. */
1293         [o_outline_view collapseItem: o_item collapseChildren: YES];
1294         [o_outline_view expandItem: o_item expandChildren: YES];
1295
1296         selectedRows = [o_outline_view selectedRowIndexes];
1297         [selectedRows getIndexes:indexes maxCount:count inIndexRange:nil];
1298     }
1299 }
1300
1301 - (NSMenu *)menuForEvent:(NSEvent *)o_event
1302 {
1303     if (!b_playlistmenu_nib_loaded)
1304         b_playlistmenu_nib_loaded = [NSBundle loadNibNamed:@"PlaylistMenu" owner:self];
1305
1306     NSPoint pt;
1307     bool b_rows;
1308     bool b_item_sel;
1309
1310     pt = [o_outline_view convertPoint: [o_event locationInWindow] fromView: nil];
1311     int row = [o_outline_view rowAtPoint:pt];
1312     if (row != -1 && ![[o_outline_view selectedRowIndexes] containsIndex: row])
1313         [o_outline_view selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO];
1314
1315     b_item_sel = (row != -1 && [o_outline_view selectedRow] != -1);
1316     b_rows = [o_outline_view numberOfRows] != 0;
1317
1318     [o_mi_play setEnabled: b_item_sel];
1319     [o_mi_delete setEnabled: b_item_sel];
1320     [o_mi_selectall setEnabled: b_rows];
1321     [o_mi_info setEnabled: b_item_sel];
1322     [o_mi_preparse setEnabled: b_item_sel];
1323     [o_mi_recursive_expand setEnabled: b_item_sel];
1324     [o_mi_sort_name setEnabled: b_item_sel];
1325     [o_mi_sort_author setEnabled: b_item_sel];
1326
1327     return o_ctx_menu;
1328 }
1329
1330 - (void)outlineView: (NSOutlineView *)o_tv didClickTableColumn:(NSTableColumn *)o_tc
1331 {
1332     int i_mode, i_type = 0;
1333     intf_thread_t *p_intf = VLCIntf;
1334     NSString * o_identifier = [o_tc identifier];
1335
1336     playlist_t *p_playlist = pl_Get(p_intf);
1337
1338     if ([o_identifier isEqualToString:TRACKNUM_COLUMN])
1339         i_mode = SORT_TRACK_NUMBER;
1340     else if ([o_identifier isEqualToString:TITLE_COLUMN])
1341         i_mode = SORT_TITLE;
1342     else if ([o_identifier isEqualToString:ARTIST_COLUMN])
1343         i_mode = SORT_ARTIST;
1344     else if ([o_identifier isEqualToString:GENRE_COLUMN])
1345         i_mode = SORT_GENRE;
1346     else if ([o_identifier isEqualToString:DURATION_COLUMN])
1347         i_mode = SORT_DURATION;
1348     else if ([o_identifier isEqualToString:ALBUM_COLUMN])
1349         i_mode = SORT_ALBUM;
1350     else if ([o_identifier isEqualToString:DESCRIPTION_COLUMN])
1351         i_mode = SORT_DESCRIPTION;
1352     else if ([o_identifier isEqualToString:URI_COLUMN])
1353         i_mode = SORT_URI;
1354     else
1355         return;
1356
1357     if (o_tc_sortColumn == o_tc)
1358         b_isSortDescending = !b_isSortDescending;
1359     else
1360         b_isSortDescending = false;
1361
1362     if (b_isSortDescending)
1363         i_type = ORDER_REVERSE;
1364     else
1365         i_type = ORDER_NORMAL;
1366
1367     PL_LOCK;
1368     playlist_RecursiveNodeSort(p_playlist, [self currentPlaylistRoot], i_mode, i_type);
1369     PL_UNLOCK;
1370
1371     [self playlistUpdated];
1372
1373     o_tc_sortColumn = o_tc;
1374     [o_outline_view setHighlightedTableColumn:o_tc];
1375
1376     if (b_isSortDescending)
1377         [o_outline_view setIndicatorImage:o_descendingSortingImage inTableColumn:o_tc];
1378     else
1379         [o_outline_view setIndicatorImage:o_ascendingSortingImage inTableColumn:o_tc];
1380 }
1381
1382
1383 - (void)outlineView:(NSOutlineView *)outlineView
1384                                 willDisplayCell:(id)cell
1385                                 forTableColumn:(NSTableColumn *)tableColumn
1386                                 item:(id)item
1387 {
1388     /* this method can be called when VLC is already dead, hence the extra checks */
1389     intf_thread_t * p_intf = VLCIntf;
1390     if (!p_intf)
1391         return;
1392     playlist_t *p_playlist = pl_Get(p_intf);
1393     if (!p_playlist)
1394         return;
1395
1396     id o_playing_item;
1397
1398     PL_LOCK;
1399     o_playing_item = [o_outline_dict objectForKey: [NSString stringWithFormat:@"%p",  playlist_CurrentPlayingItem(p_playlist)]];
1400     PL_UNLOCK;
1401
1402     NSFont *fontToUse;
1403     if (config_GetInt(VLCIntf, "macosx-large-text"))
1404         fontToUse = [NSFont systemFontOfSize:13.];
1405     else
1406         fontToUse = [NSFont systemFontOfSize:11.];
1407
1408     if ([self isItem: [o_playing_item pointerValue] inNode: [item pointerValue] checkItemExistence:YES locked:NO]
1409                         || [o_playing_item isEqual: item])
1410         [cell setFont: [[NSFontManager sharedFontManager] convertFont:fontToUse toHaveTrait:NSBoldFontMask]];
1411     else
1412         [cell setFont: [[NSFontManager sharedFontManager] convertFont:fontToUse toNotHaveTrait:NSBoldFontMask]];
1413 }
1414
1415 - (id)playingItem
1416 {
1417     playlist_t *p_playlist = pl_Get(VLCIntf);
1418
1419     id o_playing_item;
1420
1421     PL_LOCK;
1422     o_playing_item = [o_outline_dict objectForKey: [NSString stringWithFormat:@"%p",  playlist_CurrentPlayingItem(p_playlist)]];
1423     PL_UNLOCK;
1424
1425     return o_playing_item;
1426 }
1427
1428 - (NSArray *)draggedItems
1429 {
1430     return [[o_nodes_array arrayByAddingObjectsFromArray: o_items_array] retain];
1431 }
1432
1433 - (void)setColumn: (NSString *)o_column state: (NSInteger)i_state translationDict:(NSDictionary *)o_dict
1434 {
1435     NSTableColumn * o_work_tc;
1436
1437     if (i_state == NSOnState) {
1438         NSString *o_title = [o_dict objectForKey:o_column];
1439         if (!o_title)
1440             return;
1441
1442         o_work_tc = [[NSTableColumn alloc] initWithIdentifier: o_column];
1443         [o_work_tc setEditable: NO];
1444         [[o_work_tc dataCell] setFont: [NSFont controlContentFontOfSize:11.]];
1445
1446         [[o_work_tc headerCell] setStringValue: [o_dict objectForKey:o_column]];
1447
1448         if ([o_column isEqualToString: TRACKNUM_COLUMN]) {
1449             [o_work_tc setWidth: 20.];
1450             [o_work_tc setResizingMask: NSTableColumnNoResizing];
1451             [[o_work_tc headerCell] setStringValue: @"#"];
1452         }
1453
1454         [o_outline_view addTableColumn: o_work_tc];
1455         [o_work_tc release];
1456         [o_outline_view reloadData];
1457         [o_outline_view setNeedsDisplay: YES];
1458     }
1459     else
1460         [o_outline_view removeTableColumn: [o_outline_view tableColumnWithIdentifier: o_column]];
1461
1462     [o_outline_view setOutlineTableColumn: [o_outline_view tableColumnWithIdentifier:TITLE_COLUMN]];
1463 }
1464
1465 - (void)saveTableColumns
1466 {
1467     NSMutableArray * o_arrayToSave = [[NSMutableArray alloc] init];
1468     NSArray * o_columns = [[NSArray alloc] initWithArray:[o_outline_view tableColumns]];
1469     NSUInteger count = [o_columns count];
1470     NSTableColumn * o_currentColumn;
1471     for (NSUInteger i = 0; i < count; i++) {
1472         o_currentColumn = [o_columns objectAtIndex:i];
1473         [o_arrayToSave addObject:[NSArray arrayWithObjects:[o_currentColumn identifier], [NSNumber numberWithFloat:[o_currentColumn width]], nil]];
1474     }
1475     [[NSUserDefaults standardUserDefaults] setObject: o_arrayToSave forKey:@"PlaylistColumnSelection"];
1476     [[NSUserDefaults standardUserDefaults] synchronize];
1477     [o_columns release];
1478     [o_arrayToSave release];
1479 }
1480
1481 - (void)continuePlaybackWhereYouLeftOff:(input_thread_t *)p_input_thread
1482 {
1483     NSDictionary *recentlyPlayedFiles = [[NSUserDefaults standardUserDefaults] objectForKey:@"recentlyPlayedMedia"];
1484     if (recentlyPlayedFiles) {
1485         input_item_t *p_item = input_GetItem(p_input_thread);
1486         if (!p_item)
1487             return;
1488
1489         /* allow the user to over-write the start-time */
1490         if (p_item->i_options > 0) {
1491             for (int x = 0; x < p_item->i_options; x++) {
1492                 if (strstr(p_item->ppsz_options[x],"start-time"))
1493                     return;
1494             }
1495         }
1496
1497         char *psz_url = decode_URI(input_item_GetURI(p_item));
1498         if (!psz_url)
1499             return;
1500
1501         NSString *url = [NSString stringWithUTF8String:psz_url ? psz_url : ""];
1502         free(psz_url);
1503
1504         /* check for file existance before resuming */
1505         if (![[NSFileManager defaultManager] fileExistsAtPath:[[NSURL URLWithString:[NSString stringWithUTF8String:input_item_GetURI(p_item)]] path]])
1506             return;
1507
1508         NSNumber *lastPosition = [recentlyPlayedFiles objectForKey:url];
1509         if (lastPosition && lastPosition.intValue > 0) {
1510             vlc_value_t pos;
1511             var_Get(p_input_thread, "position", &pos);
1512             float f_current_pos = 100. * pos.f_float;
1513             long long int dur = input_item_GetDuration(p_item) / 1000000;
1514             int current_pos_in_sec = (f_current_pos * dur) / 100;
1515
1516             if (current_pos_in_sec >= lastPosition.intValue)
1517                 return;
1518
1519             int settingValue = config_GetInt(VLCIntf, "macosx-continue-playback");
1520             NSInteger returnValue = NSAlertErrorReturn;
1521
1522             if (settingValue == 0) {
1523                 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]];
1524
1525                 [[VLCCoreInteraction sharedInstance] pause];
1526                 returnValue = [theAlert runModal];
1527                 [[VLCCoreInteraction sharedInstance] playOrPause];
1528             }
1529
1530             if (returnValue == NSAlertAlternateReturn || settingValue == 2)
1531                 lastPosition = [NSNumber numberWithInt:0];
1532
1533             pos.f_float = (float)lastPosition.intValue / (float)dur;
1534             msg_Dbg(VLCIntf, "continuing playback at %2.2f", pos.f_float);
1535             var_Set(p_input_thread, "position", pos);
1536
1537             if (returnValue == NSAlertOtherReturn)
1538                 config_PutInt(VLCIntf, "macosx-continue-playback", 1);
1539         }
1540     }
1541 }
1542
1543 - (void)storePlaybackPositionForItem:(input_thread_t *)p_input_thread
1544 {
1545     input_item_t *p_item = input_GetItem(p_input_thread);
1546     if (!p_item)
1547         return;
1548
1549     char *psz_url = decode_URI(input_item_GetURI(p_item));
1550     NSString *url = [NSString stringWithUTF8String:psz_url ? psz_url : ""];
1551     free(psz_url);
1552
1553     if (url.length < 1)
1554         return;
1555
1556     if ([url rangeOfString:@"file://" options:NSCaseInsensitiveSearch].location != 0)
1557         return;
1558
1559     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
1560     NSMutableDictionary *mutDict = [[NSMutableDictionary alloc] initWithDictionary:[defaults objectForKey:@"recentlyPlayedMedia"]];
1561
1562     vlc_value_t pos;
1563     var_Get(p_input_thread, "position", &pos);
1564     float f_current_pos = 100. * pos.f_float;
1565     long long int dur = input_item_GetDuration(p_item) / 1000000;
1566     int current_pos_in_sec = (f_current_pos * dur) / 100;
1567     NSMutableArray *mediaList = [[defaults objectForKey:@"recentlyPlayedMediaList"] mutableCopy];
1568
1569     if (pos.f_float > .05 && pos.f_float < .95 && dur > 180) {
1570         [mutDict setObject:[NSNumber numberWithInt:current_pos_in_sec] forKey:url];
1571
1572         [mediaList removeObject:url];
1573         [mediaList addObject:url];
1574         NSUInteger mediaListCount = mediaList.count;
1575         if (mediaListCount > 30) {
1576             for (NSUInteger x = 0; x < mediaListCount - 30; x++) {
1577                 [mutDict removeObjectForKey:[mediaList objectAtIndex:0]];
1578                 [mediaList removeObjectAtIndex:0];
1579             }
1580         }
1581     } else {
1582         [mutDict removeObjectForKey:url];
1583         [mediaList removeObject:url];
1584     }
1585     [defaults setObject:mutDict forKey:@"recentlyPlayedMedia"];
1586     [defaults setObject:mediaList forKey:@"recentlyPlayedMediaList"];
1587     [defaults synchronize];
1588
1589     [mutDict release];
1590     [mediaList release];
1591 }
1592
1593 @end
1594
1595 @implementation VLCPlaylist (NSOutlineViewDataSource)
1596
1597 - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item
1598 {
1599     id o_value = [super outlineView: outlineView child: index ofItem: item];
1600
1601     [o_outline_dict setObject:o_value forKey:[NSString stringWithFormat:@"%p", [o_value pointerValue]]];
1602     return o_value;
1603 }
1604
1605 /* Required for drag & drop and reordering */
1606 - (BOOL)outlineView:(NSOutlineView *)outlineView writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pboard
1607 {
1608     playlist_t *p_playlist = pl_Get(VLCIntf);
1609
1610     /* First remove the items that were moved during the last drag & drop
1611        operation */
1612     [o_items_array removeAllObjects];
1613     [o_nodes_array removeAllObjects];
1614
1615     NSUInteger itemCount = [items count];
1616
1617     for (NSUInteger i = 0 ; i < itemCount ; i++) {
1618         id o_item = [items objectAtIndex:i];
1619
1620         /* Fill the items and nodes to move in 2 different arrays */
1621         if (((playlist_item_t *)[o_item pointerValue])->i_children > 0)
1622             [o_nodes_array addObject: o_item];
1623         else
1624             [o_items_array addObject: o_item];
1625     }
1626
1627     /* Now we need to check if there are selected items that are in already
1628        selected nodes. In that case, we only want to move the nodes */
1629     [self removeItemsFrom: o_nodes_array ifChildrenOf: o_nodes_array];
1630     [self removeItemsFrom: o_items_array ifChildrenOf: o_nodes_array];
1631
1632     /* We add the "VLCPlaylistItemPboardType" type to be able to recognize
1633        a Drop operation coming from the playlist. */
1634
1635     [pboard declareTypes: [NSArray arrayWithObject:@"VLCPlaylistItemPboardType"] owner: self];
1636     [pboard setData:[NSData data] forType:@"VLCPlaylistItemPboardType"];
1637
1638     return YES;
1639 }
1640
1641 - (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id <NSDraggingInfo>)info proposedItem:(id)item proposedChildIndex:(NSInteger)index
1642 {
1643     playlist_t *p_playlist = pl_Get(VLCIntf);
1644     NSPasteboard *o_pasteboard = [info draggingPasteboard];
1645
1646     if (!p_playlist) return NSDragOperationNone;
1647
1648     /* Dropping ON items is not allowed if item is not a node */
1649     if (item) {
1650         if (index == NSOutlineViewDropOnItemIndex &&
1651                 ((playlist_item_t *)[item pointerValue])->i_children == -1) {
1652             return NSDragOperationNone;
1653         }
1654     }
1655
1656     /* We refuse to drop an item in anything else than a child of the General
1657        Node. We still accept items that would be root nodes of the outlineview
1658        however, to allow drop in an empty playlist. */
1659     if (!(([self isItem: [item pointerValue] inNode: p_playlist->p_local_category checkItemExistence: NO locked: NO] ||
1660             (var_CreateGetBool(p_playlist, "media-library") && [self isItem: [item pointerValue] inNode: p_playlist->p_ml_category checkItemExistence: NO locked: NO])) || item == nil)) {
1661         return NSDragOperationNone;
1662     }
1663
1664     /* Drop from the Playlist */
1665     if ([[o_pasteboard types] containsObject: @"VLCPlaylistItemPboardType"]) {
1666         NSUInteger count = [o_nodes_array count];
1667         for (NSUInteger i = 0 ; i < count ; i++) {
1668             /* We refuse to Drop in a child of an item we are moving */
1669             if ([self isItem: [item pointerValue] inNode: [[o_nodes_array objectAtIndex:i] pointerValue] checkItemExistence: NO locked:NO]) {
1670                 return NSDragOperationNone;
1671             }
1672         }
1673         return NSDragOperationMove;
1674     }
1675     /* Drop from the Finder */
1676     else if ([[o_pasteboard types] containsObject: NSFilenamesPboardType]) {
1677         return NSDragOperationGeneric;
1678     }
1679     return NSDragOperationNone;
1680 }
1681
1682 - (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id <NSDraggingInfo>)info item:(id)item childIndex:(NSInteger)index
1683 {
1684     playlist_t * p_playlist =  pl_Get(VLCIntf);
1685     NSPasteboard *o_pasteboard = [info draggingPasteboard];
1686
1687     /* Drag & Drop inside the playlist */
1688     if ([[o_pasteboard types] containsObject: @"VLCPlaylistItemPboardType"]) {
1689         if (index == -1) // this is no valid target, sanitize to top of table
1690             index = 0;
1691
1692         int i_row = 0;
1693         playlist_item_t *p_new_parent, *p_item = NULL;
1694         NSArray *o_all_items = [o_nodes_array arrayByAddingObjectsFromArray: o_items_array];
1695         /* If the item is to be dropped as root item of the outline, make it a
1696            child of the respective general node, if is either the pl or the ml
1697            Else, choose the proposed parent as parent. */
1698         if (item == nil) {
1699             if ([self currentPlaylistRoot] == p_playlist->p_local_category || [self currentPlaylistRoot] == p_playlist->p_ml_category)
1700                 p_new_parent = [self currentPlaylistRoot];
1701             else
1702                 return NO;
1703         }
1704         else
1705             p_new_parent = [item pointerValue];
1706
1707         /* Make sure the proposed parent is a node.
1708            (This should never be true) */
1709         if (p_new_parent->i_children < 0)
1710             return NO;
1711
1712         NSUInteger count = [o_all_items count];
1713         if (count == 0)
1714             return NO;
1715
1716         playlist_item_t **pp_items = (playlist_item_t **)calloc(count, sizeof(playlist_item_t*));
1717         if (!pp_items)
1718             return NO;
1719
1720         PL_LOCK;
1721         NSUInteger j = 0;
1722         for (NSUInteger i = 0; i < count; i++) {
1723             p_item = [[o_all_items objectAtIndex:i] pointerValue];
1724             if (p_item)
1725                 pp_items[j++] = p_item;
1726         }
1727
1728         if (j == 0 || playlist_TreeMoveMany(p_playlist, j, pp_items, p_new_parent, index) != VLC_SUCCESS) {
1729             PL_UNLOCK;
1730             free(pp_items);
1731             return NO;
1732         }
1733
1734         PL_UNLOCK;
1735         free(pp_items);
1736
1737         [self playlistUpdated];
1738         i_row = [o_outline_view rowForItem:[o_outline_dict objectForKey:[NSString stringWithFormat: @"%p", [[o_all_items objectAtIndex:0] pointerValue]]]];
1739
1740         if (i_row == -1)
1741             i_row = [o_outline_view rowForItem:[o_outline_dict objectForKey:[NSString stringWithFormat: @"%p", p_new_parent]]];
1742
1743         [o_outline_view deselectAll: self];
1744         [o_outline_view selectRowIndexes:[NSIndexSet indexSetWithIndex:i_row] byExtendingSelection:NO];
1745         [o_outline_view scrollRowToVisible: i_row];
1746
1747         return YES;
1748     }
1749
1750     else if ([[o_pasteboard types] containsObject: NSFilenamesPboardType]) {
1751         if ([self currentPlaylistRoot] != p_playlist->p_local_category && [self currentPlaylistRoot] != p_playlist->p_ml_category)
1752             return NO;
1753
1754         playlist_item_t *p_node = [item pointerValue];
1755
1756         NSArray *o_values = [[o_pasteboard propertyListForType: NSFilenamesPboardType]
1757                                 sortedArrayUsingSelector: @selector(caseInsensitiveCompare:)];
1758         NSUInteger count = [o_values count];
1759         NSMutableArray *o_array = [NSMutableArray arrayWithCapacity:count];
1760         input_thread_t * p_input = pl_CurrentInput(VLCIntf);
1761
1762         if (count == 1 && p_input) {
1763             int i_result = input_AddSubtitleOSD(p_input, vlc_path2uri([[o_values objectAtIndex:0] UTF8String], NULL), true, true);
1764             vlc_object_release(p_input);
1765             if (i_result == VLC_SUCCESS)
1766                 return YES;
1767         }
1768         else if (p_input)
1769             vlc_object_release(p_input);
1770
1771         for (NSUInteger i = 0; i < count; i++) {
1772             NSDictionary *o_dic;
1773             char *psz_uri = vlc_path2uri([[o_values objectAtIndex:i] UTF8String], NULL);
1774             if (!psz_uri)
1775                 continue;
1776
1777             o_dic = [NSDictionary dictionaryWithObject:[NSString stringWithCString:psz_uri encoding:NSUTF8StringEncoding] forKey:@"ITEM_URL"];
1778
1779             free(psz_uri);
1780
1781             [o_array addObject: o_dic];
1782         }
1783
1784         if (item == nil)
1785             [self appendArray:o_array atPos:index enqueue: YES];
1786         else {
1787             assert(p_node->i_children != -1);
1788             [self appendNodeArray:o_array inNode: p_node atPos:index enqueue:YES];
1789         }
1790         return YES;
1791     }
1792     return NO;
1793 }
1794
1795 @end