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