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