]> git.sesse.net Git - vlc/blob - modules/gui/macosx/playlist.m
macosx: do not save resume data if VLC is in private mode
[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 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
1070         NSString *diskType = [VLCOpen getVolumeTypeFromMountPath: o_path];
1071         msg_Dbg(p_intf, "detected optical media of type %s in the file input", [diskType UTF8String]);
1072
1073         if ([diskType isEqualToString: kVLCMediaDVD])
1074             o_uri = [NSString stringWithFormat: @"dvdnav://%@", [VLCOpen getBSDNodeFromMountPath: o_path]];
1075         else if ([diskType isEqualToString: kVLCMediaVideoTSFolder])
1076             o_uri = [NSString stringWithFormat: @"dvdnav://%@", o_path];
1077         else if ([diskType isEqualToString: kVLCMediaAudioCD])
1078             o_uri = [NSString stringWithFormat: @"cdda://%@", [VLCOpen getBSDNodeFromMountPath: o_path]];
1079         else if ([diskType isEqualToString: kVLCMediaVCD])
1080             o_uri = [NSString stringWithFormat: @"vcd://%@#0:0", [VLCOpen getBSDNodeFromMountPath: o_path]];
1081         else if ([diskType isEqualToString: kVLCMediaSVCD])
1082             o_uri = [NSString stringWithFormat: @"vcd://%@@0:0", [VLCOpen getBSDNodeFromMountPath: o_path]];
1083         else if ([diskType isEqualToString: kVLCMediaBD] || [diskType isEqualToString: kVLCMediaBDMVFolder])
1084             o_uri = [NSString stringWithFormat: @"bluray://%@", o_path];
1085         else
1086             msg_Warn(VLCIntf, "unknown disk type, treating %s as regular input", [o_path UTF8String]);
1087
1088         p_input = input_item_New([o_uri UTF8String], [[[NSFileManager defaultManager] displayNameAtPath: o_path] UTF8String]);
1089     }
1090     else
1091         p_input = input_item_New([o_uri fileSystemRepresentation], o_name ? [o_name UTF8String] : NULL);
1092
1093     if (!p_input)
1094         return NULL;
1095
1096     if (o_options) {
1097         NSUInteger count = [o_options count];
1098         for (NSUInteger i = 0; i < count; i++)
1099             input_item_AddOption(p_input, [[o_options objectAtIndex:i] UTF8String], VLC_INPUT_OPTION_TRUSTED);
1100     }
1101
1102     /* Recent documents menu */
1103     if (o_nsurl != nil && (BOOL)config_GetInt(p_playlist, "macosx-recentitems") == YES)
1104         [[NSDocumentController sharedDocumentController] noteNewRecentDocumentURL: o_nsurl];
1105
1106     return p_input;
1107 }
1108
1109 - (void)appendArray:(NSArray*)o_array atPos:(int)i_position enqueue:(BOOL)b_enqueue
1110 {
1111     playlist_t * p_playlist = pl_Get(VLCIntf);
1112     NSUInteger count = [o_array count];
1113     BOOL b_usingPlaylist;
1114     if ([self currentPlaylistRoot] == p_playlist->p_ml_category)
1115         b_usingPlaylist = NO;
1116     else
1117         b_usingPlaylist = YES;
1118
1119     PL_LOCK;
1120     for (NSUInteger i_item = 0; i_item < count; i_item++) {
1121         input_item_t *p_input;
1122         NSDictionary *o_one_item;
1123
1124         /* Get the item */
1125         o_one_item = [o_array objectAtIndex:i_item];
1126         p_input = [self createItem: o_one_item];
1127         if (!p_input)
1128             continue;
1129
1130         /* Add the item */
1131         int returnValue = playlist_AddInput(p_playlist, p_input, PLAYLIST_INSERT, i_position == -1 ? PLAYLIST_END : i_position + i_item, b_usingPlaylist, pl_Locked);
1132         if (returnValue != VLC_SUCCESS) {
1133             vlc_gc_decref(p_input);
1134             continue;
1135         }
1136
1137         if (i_item == 0 && !b_enqueue) {
1138             playlist_item_t *p_item = playlist_ItemGetByInput(p_playlist, p_input);
1139             playlist_Control(p_playlist, PLAYLIST_VIEWPLAY, pl_Locked, p_item->p_parent, p_item);
1140         }
1141
1142         vlc_gc_decref(p_input);
1143     }
1144     PL_UNLOCK;
1145     [self playlistUpdated];
1146 }
1147
1148 - (void)appendNodeArray:(NSArray*)o_array inNode:(playlist_item_t *)p_node atPos:(int)i_position enqueue:(BOOL)b_enqueue
1149 {
1150     playlist_t * p_playlist = pl_Get(VLCIntf);
1151     NSUInteger count = [o_array count];
1152
1153     for (NSUInteger i_item = 0; i_item < count; i_item++) {
1154         input_item_t *p_input;
1155         NSDictionary *o_one_item;
1156
1157         /* Get the item */
1158         PL_LOCK;
1159         o_one_item = [o_array objectAtIndex:i_item];
1160         p_input = [self createItem: o_one_item];
1161
1162         if (!p_input)
1163             continue;
1164
1165         /* Add the item */
1166         playlist_NodeAddInput(p_playlist, p_input, p_node,
1167                                       PLAYLIST_INSERT,
1168                                       i_position == -1 ?
1169                                       PLAYLIST_END : i_position + i_item,
1170                                       pl_Locked);
1171
1172
1173         if (i_item == 0 && !b_enqueue) {
1174             playlist_item_t *p_item;
1175             p_item = playlist_ItemGetByInput(p_playlist, p_input);
1176             playlist_Control(p_playlist, PLAYLIST_VIEWPLAY, pl_Locked, p_node, p_item);
1177         }
1178         PL_UNLOCK;
1179         vlc_gc_decref(p_input);
1180     }
1181     [self playlistUpdated];
1182 }
1183
1184 - (NSMutableArray *)subSearchItem:(playlist_item_t *)p_item
1185 {
1186     playlist_t *p_playlist = pl_Get(VLCIntf);
1187     playlist_item_t *p_selected_item;
1188     int i_selected_row;
1189
1190     i_selected_row = [o_outline_view selectedRow];
1191     if (i_selected_row < 0)
1192         i_selected_row = 0;
1193
1194     p_selected_item = (playlist_item_t *)[[o_outline_view itemAtRow: i_selected_row] pointerValue];
1195
1196     for (NSUInteger i_current = 0; i_current < p_item->i_children ; i_current++) {
1197         char *psz_temp;
1198         NSString *o_current_name, *o_current_author;
1199
1200         PL_LOCK;
1201         o_current_name = [NSString stringWithUTF8String:p_item->pp_children[i_current]->p_input->psz_name];
1202         psz_temp = input_item_GetInfo(p_item->p_input, _("Meta-information"),_("Artist"));
1203         o_current_author = [NSString stringWithUTF8String:psz_temp];
1204         free(psz_temp);
1205         PL_UNLOCK;
1206
1207         if (p_selected_item == p_item->pp_children[i_current] && b_selected_item_met == NO)
1208             b_selected_item_met = YES;
1209         else if (p_selected_item == p_item->pp_children[i_current] && b_selected_item_met == YES)
1210             return NULL;
1211         else if (b_selected_item_met == YES &&
1212                     ([o_current_name rangeOfString:[o_search_field
1213                         stringValue] options:NSCaseInsensitiveSearch].length ||
1214                       [o_current_author rangeOfString:[o_search_field
1215                         stringValue] options:NSCaseInsensitiveSearch].length))
1216             /*Adds the parent items in the result array as well, so that we can
1217             expand the tree*/
1218             return [NSMutableArray arrayWithObject: [NSValue valueWithPointer: p_item->pp_children[i_current]]];
1219
1220         if (p_item->pp_children[i_current]->i_children > 0) {
1221             id o_result = [self subSearchItem:
1222                                             p_item->pp_children[i_current]];
1223             if (o_result != NULL) {
1224                 [o_result insertObject: [NSValue valueWithPointer:
1225                                 p_item->pp_children[i_current]] atIndex:0];
1226                 return o_result;
1227             }
1228         }
1229     }
1230     return NULL;
1231 }
1232
1233 - (IBAction)searchItem:(id)sender
1234 {
1235     playlist_t * p_playlist = pl_Get(VLCIntf);
1236     id o_result;
1237
1238     int i_row = -1;
1239
1240     b_selected_item_met = NO;
1241
1242     /* First, only search after the selected item:
1243      * (b_selected_item_met = NO) */
1244     o_result = [self subSearchItem:[self currentPlaylistRoot]];
1245     if (o_result == NULL)
1246         /* If the first search failed, search again from the beginning */
1247         o_result = [self subSearchItem:[self currentPlaylistRoot]];
1248
1249     if (o_result != NULL) {
1250         int i_start;
1251         if ([[o_result objectAtIndex:0] pointerValue] == p_playlist->p_local_category)
1252             i_start = 1;
1253         else
1254             i_start = 0;
1255         NSUInteger count = [o_result count];
1256
1257         for (NSUInteger i = i_start ; i < count - 1 ; i++) {
1258             [o_outline_view expandItem: [o_outline_dict objectForKey:
1259                         [NSString stringWithFormat: @"%p",
1260                         [[o_result objectAtIndex:i] pointerValue]]]];
1261         }
1262         i_row = [o_outline_view rowForItem: [o_outline_dict objectForKey:
1263                         [NSString stringWithFormat: @"%p",
1264                         [[o_result objectAtIndex:count - 1 ]
1265                         pointerValue]]]];
1266     }
1267     if (i_row > -1) {
1268         [o_outline_view selectRowIndexes:[NSIndexSet indexSetWithIndex:i_row] byExtendingSelection:NO];
1269         [o_outline_view scrollRowToVisible: i_row];
1270     }
1271 }
1272
1273 - (IBAction)recursiveExpandNode:(id)sender
1274 {
1275     NSIndexSet * selectedRows = [o_outline_view selectedRowIndexes];
1276     NSUInteger count = [selectedRows count];
1277     NSUInteger indexes[count];
1278     [selectedRows getIndexes:indexes maxCount:count inIndexRange:nil];
1279
1280     id o_item;
1281     playlist_item_t *p_item;
1282     for (NSUInteger i = 0; i < count; i++) {
1283         o_item = [o_outline_view itemAtRow: indexes[i]];
1284         p_item = (playlist_item_t *)[o_item pointerValue];
1285
1286         if (![[o_outline_view dataSource] outlineView: o_outline_view isItemExpandable: o_item])
1287             o_item = [o_outline_dict objectForKey: [NSString stringWithFormat: @"%p", p_item->p_parent]];
1288
1289         /* We need to collapse the node first, since OSX refuses to recursively
1290          expand an already expanded node, even if children nodes are collapsed. */
1291         [o_outline_view collapseItem: o_item collapseChildren: YES];
1292         [o_outline_view expandItem: o_item expandChildren: YES];
1293
1294         selectedRows = [o_outline_view selectedRowIndexes];
1295         [selectedRows getIndexes:indexes maxCount:count inIndexRange:nil];
1296     }
1297 }
1298
1299 - (NSMenu *)menuForEvent:(NSEvent *)o_event
1300 {
1301     if (!b_playlistmenu_nib_loaded)
1302         b_playlistmenu_nib_loaded = [NSBundle loadNibNamed:@"PlaylistMenu" owner:self];
1303
1304     NSPoint pt;
1305     bool b_rows;
1306     bool b_item_sel;
1307
1308     pt = [o_outline_view convertPoint: [o_event locationInWindow] fromView: nil];
1309     int row = [o_outline_view rowAtPoint:pt];
1310     if (row != -1 && ![[o_outline_view selectedRowIndexes] containsIndex: row])
1311         [o_outline_view selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO];
1312
1313     b_item_sel = (row != -1 && [o_outline_view selectedRow] != -1);
1314     b_rows = [o_outline_view numberOfRows] != 0;
1315
1316     [o_mi_play setEnabled: b_item_sel];
1317     [o_mi_delete setEnabled: b_item_sel];
1318     [o_mi_selectall setEnabled: b_rows];
1319     [o_mi_info setEnabled: b_item_sel];
1320     [o_mi_preparse setEnabled: b_item_sel];
1321     [o_mi_recursive_expand setEnabled: b_item_sel];
1322     [o_mi_sort_name setEnabled: b_item_sel];
1323     [o_mi_sort_author setEnabled: b_item_sel];
1324
1325     return o_ctx_menu;
1326 }
1327
1328 - (void)outlineView: (NSOutlineView *)o_tv didClickTableColumn:(NSTableColumn *)o_tc
1329 {
1330     int i_mode, i_type = 0;
1331     intf_thread_t *p_intf = VLCIntf;
1332     NSString * o_identifier = [o_tc identifier];
1333
1334     playlist_t *p_playlist = pl_Get(p_intf);
1335
1336     if ([o_identifier isEqualToString:TRACKNUM_COLUMN])
1337         i_mode = SORT_TRACK_NUMBER;
1338     else if ([o_identifier isEqualToString:TITLE_COLUMN])
1339         i_mode = SORT_TITLE;
1340     else if ([o_identifier isEqualToString:ARTIST_COLUMN])
1341         i_mode = SORT_ARTIST;
1342     else if ([o_identifier isEqualToString:GENRE_COLUMN])
1343         i_mode = SORT_GENRE;
1344     else if ([o_identifier isEqualToString:DURATION_COLUMN])
1345         i_mode = SORT_DURATION;
1346     else if ([o_identifier isEqualToString:ALBUM_COLUMN])
1347         i_mode = SORT_ALBUM;
1348     else if ([o_identifier isEqualToString:DESCRIPTION_COLUMN])
1349         i_mode = SORT_DESCRIPTION;
1350     else if ([o_identifier isEqualToString:URI_COLUMN])
1351         i_mode = SORT_URI;
1352     else
1353         return;
1354
1355     if (o_tc_sortColumn == o_tc)
1356         b_isSortDescending = !b_isSortDescending;
1357     else
1358         b_isSortDescending = false;
1359
1360     if (b_isSortDescending)
1361         i_type = ORDER_REVERSE;
1362     else
1363         i_type = ORDER_NORMAL;
1364
1365     PL_LOCK;
1366     playlist_RecursiveNodeSort(p_playlist, [self currentPlaylistRoot], i_mode, i_type);
1367     PL_UNLOCK;
1368
1369     [self playlistUpdated];
1370
1371     o_tc_sortColumn = o_tc;
1372     [o_outline_view setHighlightedTableColumn:o_tc];
1373
1374     if (b_isSortDescending)
1375         [o_outline_view setIndicatorImage:o_descendingSortingImage inTableColumn:o_tc];
1376     else
1377         [o_outline_view setIndicatorImage:o_ascendingSortingImage inTableColumn:o_tc];
1378 }
1379
1380
1381 - (void)outlineView:(NSOutlineView *)outlineView
1382                                 willDisplayCell:(id)cell
1383                                 forTableColumn:(NSTableColumn *)tableColumn
1384                                 item:(id)item
1385 {
1386     /* this method can be called when VLC is already dead, hence the extra checks */
1387     intf_thread_t * p_intf = VLCIntf;
1388     if (!p_intf)
1389         return;
1390     playlist_t *p_playlist = pl_Get(p_intf);
1391     if (!p_playlist)
1392         return;
1393
1394     id o_playing_item;
1395
1396     PL_LOCK;
1397     o_playing_item = [o_outline_dict objectForKey: [NSString stringWithFormat:@"%p",  playlist_CurrentPlayingItem(p_playlist)]];
1398     PL_UNLOCK;
1399
1400     NSFont *fontToUse;
1401     if (config_GetInt(VLCIntf, "macosx-large-text"))
1402         fontToUse = [NSFont systemFontOfSize:13.];
1403     else
1404         fontToUse = [NSFont systemFontOfSize:11.];
1405
1406     if ([self isItem: [o_playing_item pointerValue] inNode: [item pointerValue] checkItemExistence:YES locked:NO]
1407                         || [o_playing_item isEqual: item])
1408         [cell setFont: [[NSFontManager sharedFontManager] convertFont:fontToUse toHaveTrait:NSBoldFontMask]];
1409     else
1410         [cell setFont: [[NSFontManager sharedFontManager] convertFont:fontToUse toNotHaveTrait:NSBoldFontMask]];
1411 }
1412
1413 - (id)playingItem
1414 {
1415     playlist_t *p_playlist = pl_Get(VLCIntf);
1416
1417     id o_playing_item;
1418
1419     PL_LOCK;
1420     o_playing_item = [o_outline_dict objectForKey: [NSString stringWithFormat:@"%p",  playlist_CurrentPlayingItem(p_playlist)]];
1421     PL_UNLOCK;
1422
1423     return o_playing_item;
1424 }
1425
1426 - (NSArray *)draggedItems
1427 {
1428     return [[o_nodes_array arrayByAddingObjectsFromArray: o_items_array] retain];
1429 }
1430
1431 - (void)setColumn: (NSString *)o_column state: (NSInteger)i_state translationDict:(NSDictionary *)o_dict
1432 {
1433     NSTableColumn * o_work_tc;
1434
1435     if (i_state == NSOnState) {
1436         NSString *o_title = [o_dict objectForKey:o_column];
1437         if (!o_title)
1438             return;
1439
1440         o_work_tc = [[NSTableColumn alloc] initWithIdentifier: o_column];
1441         [o_work_tc setEditable: NO];
1442         [[o_work_tc dataCell] setFont: [NSFont controlContentFontOfSize:11.]];
1443
1444         [[o_work_tc headerCell] setStringValue: [o_dict objectForKey:o_column]];
1445
1446         if ([o_column isEqualToString: TRACKNUM_COLUMN]) {
1447             [o_work_tc setWidth: 20.];
1448             [o_work_tc setResizingMask: NSTableColumnNoResizing];
1449             [[o_work_tc headerCell] setStringValue: @"#"];
1450         }
1451
1452         [o_outline_view addTableColumn: o_work_tc];
1453         [o_work_tc release];
1454         [o_outline_view reloadData];
1455         [o_outline_view setNeedsDisplay: YES];
1456     }
1457     else
1458         [o_outline_view removeTableColumn: [o_outline_view tableColumnWithIdentifier: o_column]];
1459
1460     [o_outline_view setOutlineTableColumn: [o_outline_view tableColumnWithIdentifier:TITLE_COLUMN]];
1461 }
1462
1463 - (void)saveTableColumns
1464 {
1465     NSMutableArray * o_arrayToSave = [[NSMutableArray alloc] init];
1466     NSArray * o_columns = [[NSArray alloc] initWithArray:[o_outline_view tableColumns]];
1467     NSUInteger count = [o_columns count];
1468     NSTableColumn * o_currentColumn;
1469     for (NSUInteger i = 0; i < count; i++) {
1470         o_currentColumn = [o_columns objectAtIndex:i];
1471         [o_arrayToSave addObject:[NSArray arrayWithObjects:[o_currentColumn identifier], [NSNumber numberWithFloat:[o_currentColumn width]], nil]];
1472     }
1473     [[NSUserDefaults standardUserDefaults] setObject: o_arrayToSave forKey:@"PlaylistColumnSelection"];
1474     [[NSUserDefaults standardUserDefaults] synchronize];
1475     [o_columns release];
1476     [o_arrayToSave release];
1477 }
1478
1479 - (BOOL)isValidResumeItem:(input_item_t *)p_item
1480 {
1481     char *psz_url = input_item_GetURI(p_item);
1482     NSString *o_url_string = toNSStr(psz_url);
1483     free(psz_url);
1484
1485     if ([o_url_string isEqualToString:@""])
1486         return NO;
1487
1488     NSURL *o_url = [NSURL URLWithString:o_url_string];
1489
1490     if (![o_url isFileURL])
1491         return NO;
1492
1493     BOOL isDir = false;
1494     if (![[NSFileManager defaultManager] fileExistsAtPath:[o_url path] isDirectory:&isDir])
1495         return NO;
1496
1497     if (isDir)
1498         return NO;
1499
1500     return YES;
1501 }
1502
1503 - (void)continuePlaybackWhereYouLeftOff:(input_thread_t *)p_input_thread
1504 {
1505     NSDictionary *recentlyPlayedFiles = [[NSUserDefaults standardUserDefaults] objectForKey:@"recentlyPlayedMedia"];
1506     if (recentlyPlayedFiles) {
1507         input_item_t *p_item = input_GetItem(p_input_thread);
1508         if (!p_item)
1509             return;
1510
1511         /* allow the user to over-write the start/stop/run-time */
1512         if (var_GetFloat(p_input_thread, "run-time") > 0 ||
1513             var_GetFloat(p_input_thread, "start-time") > 0 ||
1514             var_GetFloat(p_input_thread, "stop-time") > 0) {
1515             return;
1516         }
1517
1518         /* check for file existance before resuming */
1519         if (![self isValidResumeItem:p_item])
1520             return;
1521
1522         char *psz_url = decode_URI(input_item_GetURI(p_item));
1523         if (!psz_url)
1524             return;
1525         NSString *url = toNSStr(psz_url);
1526         free(psz_url);
1527
1528         NSNumber *lastPosition = [recentlyPlayedFiles objectForKey:url];
1529         if (lastPosition && lastPosition.intValue > 0) {
1530             vlc_value_t pos;
1531             var_Get(p_input_thread, "position", &pos);
1532             float f_current_pos = 100. * pos.f_float;
1533             long long int dur = input_item_GetDuration(p_item) / 1000000;
1534             int current_pos_in_sec = (f_current_pos * dur) / 100;
1535
1536             if (current_pos_in_sec >= lastPosition.intValue)
1537                 return;
1538
1539             int settingValue = config_GetInt(VLCIntf, "macosx-continue-playback");
1540             NSInteger returnValue = NSAlertErrorReturn;
1541
1542             if (settingValue == 0) {
1543                 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]];
1544
1545                 [[VLCCoreInteraction sharedInstance] pause];
1546                 returnValue = [theAlert runModal];
1547                 [[VLCCoreInteraction sharedInstance] playOrPause];
1548             }
1549
1550             if (returnValue == NSAlertAlternateReturn || settingValue == 2)
1551                 lastPosition = [NSNumber numberWithInt:0];
1552
1553             pos.f_float = (float)lastPosition.intValue / (float)dur;
1554             msg_Dbg(VLCIntf, "continuing playback at %2.2f", pos.f_float);
1555             var_Set(p_input_thread, "position", pos);
1556
1557             if (returnValue == NSAlertOtherReturn)
1558                 config_PutInt(VLCIntf, "macosx-continue-playback", 1);
1559         }
1560     }
1561 }
1562
1563 - (void)storePlaybackPositionForItem:(input_thread_t *)p_input_thread
1564 {
1565     if (!var_InheritBool(VLCIntf, "macosx-recentitems"))
1566         return;
1567
1568     input_item_t *p_item = input_GetItem(p_input_thread);
1569     if (!p_item)
1570         return;
1571
1572     if (![self isValidResumeItem:p_item])
1573         return;
1574
1575     char *psz_url = decode_URI(input_item_GetURI(p_item));
1576     if (!psz_url)
1577         return;
1578     NSString *url = toNSStr(psz_url);
1579     free(psz_url);
1580
1581     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
1582     NSMutableDictionary *mutDict = [[NSMutableDictionary alloc] initWithDictionary:[defaults objectForKey:@"recentlyPlayedMedia"]];
1583
1584     vlc_value_t pos;
1585     var_Get(p_input_thread, "position", &pos);
1586     float f_current_pos = 100. * pos.f_float;
1587     long long int dur = input_item_GetDuration(p_item) / 1000000;
1588     int current_pos_in_sec = (f_current_pos * dur) / 100;
1589     NSMutableArray *mediaList = [[defaults objectForKey:@"recentlyPlayedMediaList"] mutableCopy];
1590
1591     if (pos.f_float > .05 && pos.f_float < .95 && dur > 180) {
1592         [mutDict setObject:[NSNumber numberWithInt:current_pos_in_sec] forKey:url];
1593
1594         [mediaList removeObject:url];
1595         [mediaList addObject:url];
1596         NSUInteger mediaListCount = mediaList.count;
1597         if (mediaListCount > 30) {
1598             for (NSUInteger x = 0; x < mediaListCount - 30; x++) {
1599                 [mutDict removeObjectForKey:[mediaList objectAtIndex:0]];
1600                 [mediaList removeObjectAtIndex:0];
1601             }
1602         }
1603     } else {
1604         [mutDict removeObjectForKey:url];
1605         [mediaList removeObject:url];
1606     }
1607     [defaults setObject:mutDict forKey:@"recentlyPlayedMedia"];
1608     [defaults setObject:mediaList forKey:@"recentlyPlayedMediaList"];
1609     [defaults synchronize];
1610
1611     [mutDict release];
1612     [mediaList release];
1613 }
1614
1615 @end
1616
1617 @implementation VLCPlaylist (NSOutlineViewDataSource)
1618
1619 - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item
1620 {
1621     id o_value = [super outlineView: outlineView child: index ofItem: item];
1622
1623     [o_outline_dict setObject:o_value forKey:[NSString stringWithFormat:@"%p", [o_value pointerValue]]];
1624     return o_value;
1625 }
1626
1627 /* Required for drag & drop and reordering */
1628 - (BOOL)outlineView:(NSOutlineView *)outlineView writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pboard
1629 {
1630     playlist_t *p_playlist = pl_Get(VLCIntf);
1631
1632     /* First remove the items that were moved during the last drag & drop
1633        operation */
1634     [o_items_array removeAllObjects];
1635     [o_nodes_array removeAllObjects];
1636
1637     NSUInteger itemCount = [items count];
1638
1639     for (NSUInteger i = 0 ; i < itemCount ; i++) {
1640         id o_item = [items objectAtIndex:i];
1641
1642         /* Fill the items and nodes to move in 2 different arrays */
1643         if (((playlist_item_t *)[o_item pointerValue])->i_children > 0)
1644             [o_nodes_array addObject: o_item];
1645         else
1646             [o_items_array addObject: o_item];
1647     }
1648
1649     /* Now we need to check if there are selected items that are in already
1650        selected nodes. In that case, we only want to move the nodes */
1651     [self removeItemsFrom: o_nodes_array ifChildrenOf: o_nodes_array];
1652     [self removeItemsFrom: o_items_array ifChildrenOf: o_nodes_array];
1653
1654     /* We add the "VLCPlaylistItemPboardType" type to be able to recognize
1655        a Drop operation coming from the playlist. */
1656
1657     [pboard declareTypes: [NSArray arrayWithObject:@"VLCPlaylistItemPboardType"] owner: self];
1658     [pboard setData:[NSData data] forType:@"VLCPlaylistItemPboardType"];
1659
1660     return YES;
1661 }
1662
1663 - (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id <NSDraggingInfo>)info proposedItem:(id)item proposedChildIndex:(NSInteger)index
1664 {
1665     playlist_t *p_playlist = pl_Get(VLCIntf);
1666     NSPasteboard *o_pasteboard = [info draggingPasteboard];
1667
1668     if (!p_playlist) return NSDragOperationNone;
1669
1670     /* Dropping ON items is not allowed if item is not a node */
1671     if (item) {
1672         if (index == NSOutlineViewDropOnItemIndex &&
1673                 ((playlist_item_t *)[item pointerValue])->i_children == -1) {
1674             return NSDragOperationNone;
1675         }
1676     }
1677
1678     /* We refuse to drop an item in anything else than a child of the General
1679        Node. We still accept items that would be root nodes of the outlineview
1680        however, to allow drop in an empty playlist. */
1681     if (!(([self isItem: [item pointerValue] inNode: p_playlist->p_local_category checkItemExistence: NO locked: NO] ||
1682             (var_CreateGetBool(p_playlist, "media-library") && [self isItem: [item pointerValue] inNode: p_playlist->p_ml_category checkItemExistence: NO locked: NO])) || item == nil)) {
1683         return NSDragOperationNone;
1684     }
1685
1686     /* Drop from the Playlist */
1687     if ([[o_pasteboard types] containsObject: @"VLCPlaylistItemPboardType"]) {
1688         NSUInteger count = [o_nodes_array count];
1689         for (NSUInteger i = 0 ; i < count ; i++) {
1690             /* We refuse to Drop in a child of an item we are moving */
1691             if ([self isItem: [item pointerValue] inNode: [[o_nodes_array objectAtIndex:i] pointerValue] checkItemExistence: NO locked:NO]) {
1692                 return NSDragOperationNone;
1693             }
1694         }
1695         return NSDragOperationMove;
1696     }
1697     /* Drop from the Finder */
1698     else if ([[o_pasteboard types] containsObject: NSFilenamesPboardType]) {
1699         return NSDragOperationGeneric;
1700     }
1701     return NSDragOperationNone;
1702 }
1703
1704 - (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id <NSDraggingInfo>)info item:(id)item childIndex:(NSInteger)index
1705 {
1706     playlist_t * p_playlist =  pl_Get(VLCIntf);
1707     NSPasteboard *o_pasteboard = [info draggingPasteboard];
1708
1709     /* Drag & Drop inside the playlist */
1710     if ([[o_pasteboard types] containsObject: @"VLCPlaylistItemPboardType"]) {
1711         if (index == -1) // this is no valid target, sanitize to top of table
1712             index = 0;
1713
1714         int i_row = 0;
1715         playlist_item_t *p_new_parent, *p_item = NULL;
1716         NSArray *o_all_items = [o_nodes_array arrayByAddingObjectsFromArray: o_items_array];
1717         /* If the item is to be dropped as root item of the outline, make it a
1718            child of the respective general node, if is either the pl or the ml
1719            Else, choose the proposed parent as parent. */
1720         if (item == nil) {
1721             if ([self currentPlaylistRoot] == p_playlist->p_local_category || [self currentPlaylistRoot] == p_playlist->p_ml_category)
1722                 p_new_parent = [self currentPlaylistRoot];
1723             else
1724                 return NO;
1725         }
1726         else
1727             p_new_parent = [item pointerValue];
1728
1729         /* Make sure the proposed parent is a node.
1730            (This should never be true) */
1731         if (p_new_parent->i_children < 0)
1732             return NO;
1733
1734         NSUInteger count = [o_all_items count];
1735         if (count == 0)
1736             return NO;
1737
1738         playlist_item_t **pp_items = (playlist_item_t **)calloc(count, sizeof(playlist_item_t*));
1739         if (!pp_items)
1740             return NO;
1741
1742         PL_LOCK;
1743         NSUInteger j = 0;
1744         for (NSUInteger i = 0; i < count; i++) {
1745             p_item = [[o_all_items objectAtIndex:i] pointerValue];
1746             if (p_item)
1747                 pp_items[j++] = p_item;
1748         }
1749
1750         if (j == 0 || playlist_TreeMoveMany(p_playlist, j, pp_items, p_new_parent, index) != VLC_SUCCESS) {
1751             PL_UNLOCK;
1752             free(pp_items);
1753             return NO;
1754         }
1755
1756         PL_UNLOCK;
1757         free(pp_items);
1758
1759         [self playlistUpdated];
1760         i_row = [o_outline_view rowForItem:[o_outline_dict objectForKey:[NSString stringWithFormat: @"%p", [[o_all_items objectAtIndex:0] pointerValue]]]];
1761
1762         if (i_row == -1)
1763             i_row = [o_outline_view rowForItem:[o_outline_dict objectForKey:[NSString stringWithFormat: @"%p", p_new_parent]]];
1764
1765         [o_outline_view deselectAll: self];
1766         [o_outline_view selectRowIndexes:[NSIndexSet indexSetWithIndex:i_row] byExtendingSelection:NO];
1767         [o_outline_view scrollRowToVisible: i_row];
1768
1769         return YES;
1770     }
1771
1772     else if ([[o_pasteboard types] containsObject: NSFilenamesPboardType]) {
1773         if ([self currentPlaylistRoot] != p_playlist->p_local_category && [self currentPlaylistRoot] != p_playlist->p_ml_category)
1774             return NO;
1775
1776         playlist_item_t *p_node = [item pointerValue];
1777
1778         NSArray *o_values = [[o_pasteboard propertyListForType: NSFilenamesPboardType]
1779                                 sortedArrayUsingSelector: @selector(caseInsensitiveCompare:)];
1780         NSUInteger count = [o_values count];
1781         NSMutableArray *o_array = [NSMutableArray arrayWithCapacity:count];
1782         input_thread_t * p_input = pl_CurrentInput(VLCIntf);
1783
1784         if (count == 1 && p_input) {
1785             int i_result = input_AddSubtitleOSD(p_input, vlc_path2uri([[o_values objectAtIndex:0] UTF8String], NULL), true, true);
1786             vlc_object_release(p_input);
1787             if (i_result == VLC_SUCCESS)
1788                 return YES;
1789         }
1790         else if (p_input)
1791             vlc_object_release(p_input);
1792
1793         for (NSUInteger i = 0; i < count; i++) {
1794             NSDictionary *o_dic;
1795             char *psz_uri = vlc_path2uri([[o_values objectAtIndex:i] UTF8String], NULL);
1796             if (!psz_uri)
1797                 continue;
1798
1799             o_dic = [NSDictionary dictionaryWithObject:[NSString stringWithCString:psz_uri encoding:NSUTF8StringEncoding] forKey:@"ITEM_URL"];
1800
1801             free(psz_uri);
1802
1803             [o_array addObject: o_dic];
1804         }
1805
1806         if (item == nil)
1807             [self appendArray:o_array atPos:index enqueue: YES];
1808         else {
1809             assert(p_node->i_children != -1);
1810             [self appendNodeArray:o_array inNode: p_node atPos:index enqueue:YES];
1811         }
1812         return YES;
1813     }
1814     return NO;
1815 }
1816
1817 @end