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