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