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