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