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