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