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