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