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