Check dialog return value
[vlc.git] / 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         bool  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 == true )
502     {
503         [[[VLCMain sharedInstance] getControls] repeatOne];
504    }
505     else if( val2.b_bool == 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, 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_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     o_to_delete = [NSMutableArray arrayWithArray:[[o_outline_view selectedRowEnumerator] allObjects]];
819     i_count = [o_to_delete count];
820
821     p_playlist = pl_Yield( p_intf );
822
823     PL_LOCK;
824     for( int i = 0; i < i_count; i++ )
825     {
826         o_number = [o_to_delete lastObject];
827         i_row = [o_number intValue];
828         id o_item = [o_outline_view itemAtRow: i_row];
829         playlist_item_t *p_item = [o_item pointerValue];
830 #ifndef NDEBUG
831         msg_Dbg( p_intf, "deleting item %i (of %i) with id \"%i\", pointerValue \"%p\" and %i children", i+1, i_count, 
832                 p_item->p_input->i_id, [o_item pointerValue], p_item->i_children +1 );
833 #endif
834         [o_to_delete removeObject: o_number];
835         [o_outline_view deselectRow: i_row];
836
837         if( p_item->i_children != -1 )
838         //is a node and not an item
839         {
840             if( p_playlist->status.i_status != PLAYLIST_STOPPED &&
841                 [self isItem: p_playlist->status.p_item inNode:
842                         ((playlist_item_t *)[o_item pointerValue])
843                         checkItemExistence: NO] == YES )
844                 // if current item is in selected node and is playing then stop playlist
845                 playlist_Stop( p_playlist );
846     
847             playlist_NodeDelete( p_playlist, p_item, true, false );
848         }
849         else
850             playlist_DeleteFromInput( p_playlist, p_item->p_input->i_id, true );
851     }
852     PL_UNLOCK;
853
854     [self playlistUpdated];
855     vlc_object_release( p_playlist );
856 }
857
858 - (IBAction)sortNodeByName:(id)sender
859 {
860     [self sortNode: SORT_TITLE];
861 }
862
863 - (IBAction)sortNodeByAuthor:(id)sender
864 {
865     [self sortNode: SORT_ARTIST];
866 }
867
868 - (void)sortNode:(int)i_mode
869 {
870     playlist_t * p_playlist = pl_Yield( VLCIntf );
871     playlist_item_t * p_item;
872
873     if( [o_outline_view selectedRow] > -1 )
874     {
875         p_item = [[o_outline_view itemAtRow: [o_outline_view selectedRow]] 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 stringWithUTF8String: mounts[i_index].f_mntfromname];
930             [o_temp replaceOccurrencesOfString: @"/dev/rdisk" withString: @"/dev/disk" options:NSLiteralSearch range:NSMakeRange(0, [o_temp length]) ];
931             [o_temp2 replaceOccurrencesOfString: @"s0" withString: @"" options:NSLiteralSearch range:NSMakeRange(0, [o_temp2 length]) ];
932             [o_temp2 replaceOccurrencesOfString: @"s1" withString: @"" options:NSLiteralSearch 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 stringWithUTF8String: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 stringWithUTF8String: psz_dev ];
957         [o_temp replaceOccurrencesOfString: @"/dev/disk" withString: @"/dev/rdisk" options:NSLiteralSearch range:NSMakeRange(0, [o_temp length]) ];
958         [o_temp replaceOccurrencesOfString: @"s0" withString: @"" options:NSLiteralSearch range:NSMakeRange(0, [o_temp length]) ];
959         [o_temp replaceOccurrencesOfString: @"s1" withString: @"" options:NSLiteralSearch 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 && (BOOL)config_GetInt( p_playlist, "macosx-recentitems" ) == YES )
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         /* FIXME: playlist_AddInput() can fail */
1007         playlist_AddInput( p_playlist, p_input, PLAYLIST_INSERT,
1008              i_position == -1 ? PLAYLIST_END : i_position + i_item, true,
1009          false );
1010
1011         if( i_item == 0 && !b_enqueue )
1012         {
1013             playlist_item_t *p_item;
1014             p_item = playlist_ItemGetByInput( p_playlist, p_input, true );
1015             playlist_Control( p_playlist, PLAYLIST_VIEWPLAY, true, NULL, p_item );
1016         }
1017         else
1018         {
1019             playlist_item_t *p_item;
1020             p_item = playlist_ItemGetByInput( p_playlist, p_input, true );
1021             playlist_Control( p_playlist, PLAYLIST_SKIP, true, p_item );
1022         }
1023         vlc_gc_decref( p_input );
1024     }
1025     [self playlistUpdated];
1026     vlc_object_release( p_playlist );
1027 }
1028
1029 - (void)appendNodeArray:(NSArray*)o_array inNode:(playlist_item_t *)p_node atPos:(int)i_position enqueue:(BOOL)b_enqueue
1030 {
1031     int i_item;
1032     playlist_t * p_playlist = pl_Yield( VLCIntf );
1033
1034     for( i_item = 0; i_item < (int)[o_array count]; i_item++ )
1035     {
1036         input_item_t *p_input;
1037         NSDictionary *o_one_item;
1038
1039         /* Get the item */
1040         o_one_item = [o_array objectAtIndex: i_item];
1041         p_input = [self createItem: o_one_item];
1042         if( !p_input )
1043         {
1044             continue;
1045         }
1046
1047         /* Add the item */
1048         /* FIXME: playlist_NodeAddInput() can fail */
1049        playlist_NodeAddInput( p_playlist, p_input, p_node,
1050                                       PLAYLIST_INSERT,
1051                                       i_position == -1 ?
1052                                       PLAYLIST_END : i_position + i_item, false );
1053
1054
1055         if( i_item == 0 && !b_enqueue )
1056         {
1057             playlist_item_t *p_item;
1058             p_item = playlist_ItemGetByInput( p_playlist, p_input, true );
1059             playlist_Control( p_playlist, PLAYLIST_VIEWPLAY, true, NULL, p_item );
1060         }
1061         else
1062         {
1063             playlist_item_t *p_item;
1064             p_item = playlist_ItemGetByInput( p_playlist, p_input, true );
1065             playlist_Control( p_playlist, PLAYLIST_SKIP, true, p_item );
1066         }
1067         vlc_gc_decref( p_input );
1068     }
1069     [self playlistUpdated];
1070     vlc_object_release( p_playlist );
1071 }
1072
1073 - (NSMutableArray *)subSearchItem:(playlist_item_t *)p_item
1074 {
1075     playlist_t *p_playlist = pl_Yield( VLCIntf );
1076     playlist_item_t *p_selected_item;
1077     int i_current, i_selected_row;
1078
1079     i_selected_row = [o_outline_view selectedRow];
1080     if (i_selected_row < 0)
1081         i_selected_row = 0;
1082
1083     p_selected_item = (playlist_item_t *)[[o_outline_view itemAtRow:
1084                                             i_selected_row] pointerValue];
1085
1086     for( i_current = 0; i_current < p_item->i_children ; i_current++ )
1087     {
1088         char *psz_temp;
1089         NSString *o_current_name, *o_current_author;
1090
1091         vlc_mutex_lock( &p_playlist->object_lock );
1092         o_current_name = [NSString stringWithUTF8String:
1093             p_item->pp_children[i_current]->p_input->psz_name];
1094         psz_temp = input_ItemGetInfo( p_item->p_input ,
1095                    _("Meta-information"),_("Artist") );
1096         o_current_author = [NSString stringWithUTF8String: psz_temp];
1097         free( psz_temp);
1098         vlc_mutex_unlock( &p_playlist->object_lock );
1099
1100         if( p_selected_item == p_item->pp_children[i_current] &&
1101                     b_selected_item_met == NO )
1102         {
1103             b_selected_item_met = YES;
1104         }
1105         else if( p_selected_item == p_item->pp_children[i_current] &&
1106                     b_selected_item_met == YES )
1107         {
1108             vlc_object_release( p_playlist );
1109             return NULL;
1110         }
1111         else if( b_selected_item_met == YES &&
1112                     ( [o_current_name rangeOfString:[o_search_field
1113                         stringValue] options:NSCaseInsensitiveSearch ].length ||
1114                       [o_current_author rangeOfString:[o_search_field
1115                         stringValue] options:NSCaseInsensitiveSearch ].length ) )
1116         {
1117             vlc_object_release( p_playlist );
1118             /*Adds the parent items in the result array as well, so that we can
1119             expand the tree*/
1120             return [NSMutableArray arrayWithObject: [NSValue
1121                             valueWithPointer: p_item->pp_children[i_current]]];
1122         }
1123         if( p_item->pp_children[i_current]->i_children > 0 )
1124         {
1125             id o_result = [self subSearchItem:
1126                                             p_item->pp_children[i_current]];
1127             if( o_result != NULL )
1128             {
1129                 vlc_object_release( p_playlist );
1130                 [o_result insertObject: [NSValue valueWithPointer:
1131                                 p_item->pp_children[i_current]] atIndex:0];
1132                 return o_result;
1133             }
1134         }
1135     }
1136     vlc_object_release( p_playlist );
1137     return NULL;
1138 }
1139
1140 - (IBAction)searchItem:(id)sender
1141 {
1142     playlist_t * p_playlist = pl_Yield( VLCIntf );
1143     id o_result;
1144
1145     unsigned int i;
1146     int i_row = -1;
1147
1148     b_selected_item_met = NO;
1149
1150         /*First, only search after the selected item:*
1151          *(b_selected_item_met = NO)                 */
1152     o_result = [self subSearchItem:p_playlist->p_root_category];
1153     if( o_result == NULL )
1154     {
1155         /* If the first search failed, search again from the beginning */
1156         o_result = [self subSearchItem:p_playlist->p_root_category];
1157     }
1158     if( o_result != NULL )
1159     {
1160         int i_start;
1161         if( [[o_result objectAtIndex: 0] pointerValue] ==
1162                                                     p_playlist->p_local_category )
1163         i_start = 1;
1164         else
1165         i_start = 0;
1166
1167         for( i = i_start ; i < [o_result count] - 1 ; i++ )
1168         {
1169             [o_outline_view expandItem: [o_outline_dict objectForKey:
1170                         [NSString stringWithFormat: @"%p",
1171                         [[o_result objectAtIndex: i] pointerValue]]]];
1172         }
1173         i_row = [o_outline_view rowForItem: [o_outline_dict objectForKey:
1174                         [NSString stringWithFormat: @"%p",
1175                         [[o_result objectAtIndex: [o_result count] - 1 ]
1176                         pointerValue]]]];
1177     }
1178     if( i_row > -1 )
1179     {
1180         [o_outline_view selectRow:i_row byExtendingSelection: NO];
1181         [o_outline_view scrollRowToVisible: i_row];
1182     }
1183     vlc_object_release( p_playlist );
1184 }
1185
1186 - (IBAction)recursiveExpandNode:(id)sender
1187 {
1188     id o_item = [o_outline_view itemAtRow: [o_outline_view selectedRow]];
1189     playlist_item_t *p_item = (playlist_item_t *)[o_item pointerValue];
1190
1191     if( ![[o_outline_view dataSource] outlineView: o_outline_view
1192                                                     isItemExpandable: o_item] )
1193     {
1194         o_item = [o_outline_dict objectForKey: [NSString
1195                    stringWithFormat: @"%p", p_item->p_parent]];
1196     }
1197
1198     /* We need to collapse the node first, since OSX refuses to recursively
1199        expand an already expanded node, even if children nodes are collapsed. */
1200     [o_outline_view collapseItem: o_item collapseChildren: YES];
1201     [o_outline_view expandItem: o_item expandChildren: YES];
1202 }
1203
1204 - (NSMenu *)menuForEvent:(NSEvent *)o_event
1205 {
1206     NSPoint pt;
1207     bool b_rows;
1208     bool b_item_sel;
1209
1210     pt = [o_outline_view convertPoint: [o_event locationInWindow]
1211                                                  fromView: nil];
1212     b_item_sel = ( [o_outline_view rowAtPoint: pt] != -1 &&
1213                    [o_outline_view selectedRow] != -1 );
1214     b_rows = [o_outline_view numberOfRows] != 0;
1215
1216     [o_mi_play setEnabled: b_item_sel];
1217     [o_mi_delete setEnabled: b_item_sel];
1218     [o_mi_selectall setEnabled: b_rows];
1219     [o_mi_info setEnabled: b_item_sel];
1220     [o_mi_preparse setEnabled: b_item_sel];
1221     [o_mi_recursive_expand setEnabled: b_item_sel];
1222     [o_mi_sort_name setEnabled: b_item_sel];
1223     [o_mi_sort_author setEnabled: b_item_sel];
1224
1225     return( o_ctx_menu );
1226 }
1227
1228 - (void)outlineView: (NSTableView*)o_tv
1229                   didClickTableColumn:(NSTableColumn *)o_tc
1230 {
1231     int i_mode = 0, i_type;
1232     intf_thread_t *p_intf = VLCIntf;
1233
1234     playlist_t *p_playlist = pl_Yield( p_intf );
1235
1236     /* Check whether the selected table column header corresponds to a
1237        sortable table column*/
1238     if( !( o_tc == o_tc_name || o_tc == o_tc_author ) )
1239     {
1240         vlc_object_release( p_playlist );
1241         return;
1242     }
1243
1244     if( o_tc_sortColumn == o_tc )
1245     {
1246         b_isSortDescending = !b_isSortDescending;
1247     }
1248     else
1249     {
1250         b_isSortDescending = false;
1251     }
1252
1253     if( o_tc == o_tc_name )
1254     {
1255         i_mode = SORT_TITLE;
1256     }
1257     else if( o_tc == o_tc_author )
1258     {
1259         i_mode = SORT_ARTIST;
1260     }
1261
1262     if( b_isSortDescending )
1263     {
1264         i_type = ORDER_REVERSE;
1265     }
1266     else
1267     {
1268         i_type = ORDER_NORMAL;
1269     }
1270
1271     vlc_mutex_lock( &p_playlist->object_lock );
1272     playlist_RecursiveNodeSort( p_playlist, p_playlist->p_root_category, i_mode, i_type );
1273     vlc_mutex_unlock( &p_playlist->object_lock );
1274
1275     vlc_object_release( p_playlist );
1276     [self playlistUpdated];
1277
1278     o_tc_sortColumn = o_tc;
1279     [o_outline_view setHighlightedTableColumn:o_tc];
1280
1281     if( b_isSortDescending )
1282     {
1283         [o_outline_view setIndicatorImage:o_descendingSortingImage
1284                                                         inTableColumn:o_tc];
1285     }
1286     else
1287     {
1288         [o_outline_view setIndicatorImage:o_ascendingSortingImage
1289                                                         inTableColumn:o_tc];
1290     }
1291 }
1292
1293
1294 - (void)outlineView:(NSOutlineView *)outlineView
1295                                 willDisplayCell:(id)cell
1296                                 forTableColumn:(NSTableColumn *)tableColumn
1297                                 item:(id)item
1298 {
1299     playlist_t *p_playlist = pl_Yield( VLCIntf );
1300
1301     id o_playing_item;
1302
1303     o_playing_item = [o_outline_dict objectForKey:
1304                 [NSString stringWithFormat:@"%p",  p_playlist->status.p_item]];
1305
1306     if( [self isItem: [o_playing_item pointerValue] inNode:
1307                         [item pointerValue] checkItemExistence: YES]
1308                         || [o_playing_item isEqual: item] )
1309     {
1310         [cell setFont: [NSFont boldSystemFontOfSize: 0]];
1311     }
1312     else
1313     {
1314         [cell setFont: [NSFont systemFontOfSize: 0]];
1315     }
1316     vlc_object_release( p_playlist );
1317 }
1318
1319 - (IBAction)addNode:(id)sender
1320 {
1321     /* we have to create a new thread here because otherwise we would block the
1322      * interface since the interaction-stuff and this code would run in the same
1323      * thread */
1324     [NSThread detachNewThreadSelector: @selector(addNodeThreadedly)
1325         toTarget: self withObject:nil];
1326     [self playlistUpdated];
1327 }
1328
1329 - (void)addNodeThreadedly
1330 {
1331     NSAutoreleasePool * ourPool = [[NSAutoreleasePool alloc] init];
1332
1333     /* simply adds a new node to the end of the playlist */
1334     playlist_t * p_playlist = pl_Yield( VLCIntf );
1335     vlc_thread_set_priority( p_playlist, VLC_THREAD_PRIORITY_LOW );
1336
1337     int ret_v;
1338     char *psz_name = NULL;
1339     playlist_item_t * p_item;
1340     ret_v = intf_UserStringInput( p_playlist, _("New Node"),
1341         _("Please enter a name for the new node."), &psz_name );
1342
1343     if( ret_v != DIALOG_CANCELLED && psz_name && *psz_name )
1344         p_item = playlist_NodeCreate( p_playlist, psz_name,
1345                                       p_playlist->p_local_category, 0, NULL );
1346     else if(! config_GetInt( p_playlist, "interact" ) )
1347     {
1348         /* in case that the interaction is disabled, just give it a bogus name */
1349         p_item = playlist_NodeCreate( p_playlist, _("Empty Folder"),
1350                                       p_playlist->p_local_category, 0, NULL );
1351     }
1352
1353     if(! p_item )
1354         msg_Warn( VLCIntf, "node creation failed or cancelled by user" );
1355
1356     vlc_object_release( p_playlist );
1357     [ourPool release];
1358 }
1359
1360 @end
1361
1362 @implementation VLCPlaylist (NSOutlineViewDataSource)
1363
1364 - (id)outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item
1365 {
1366     id o_value = [super outlineView: outlineView child: index ofItem: item];
1367     playlist_t *p_playlist = pl_Yield( VLCIntf );
1368
1369     if( playlist_CurrentSize( p_playlist )  >= 2 )
1370     {
1371         [o_status_field setStringValue: [NSString stringWithFormat:
1372                     _NS("%i items in the playlist"),
1373              playlist_CurrentSize( p_playlist )]];
1374     }
1375     else
1376     {
1377         if( playlist_IsEmpty( p_playlist ) )
1378         {
1379             [o_status_field setStringValue: _NS("No items in the playlist")];
1380         }
1381         else
1382         {
1383             [o_status_field setStringValue: _NS("1 item in the playlist")];
1384         }
1385     }
1386     vlc_object_release( p_playlist );
1387
1388     [o_outline_dict setObject:o_value forKey:[NSString stringWithFormat:@"%p",
1389                                                     [o_value pointerValue]]];
1390 #ifndef NDEBUG
1391     msg_Dbg( VLCIntf, "adding item %p", [o_value pointerValue] );
1392 #endif
1393     return o_value;
1394
1395 }
1396
1397 /* Required for drag & drop and reordering */
1398 - (BOOL)outlineView:(NSOutlineView *)outlineView writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pboard
1399 {
1400     unsigned int i;
1401     playlist_t *p_playlist = pl_Yield( VLCIntf );
1402
1403     /* First remove the items that were moved during the last drag & drop
1404        operation */
1405     [o_items_array removeAllObjects];
1406     [o_nodes_array removeAllObjects];
1407
1408     for( i = 0 ; i < [items count] ; i++ )
1409     {
1410         id o_item = [items objectAtIndex: i];
1411
1412         /* Refuse to move items that are not in the General Node
1413            (Service Discovery) */
1414         if( ![self isItem: [o_item pointerValue] inNode:
1415                         p_playlist->p_local_category checkItemExistence: NO] &&
1416             ( var_CreateGetBool( p_playlist, "media-library" ) &&
1417             ![self isItem: [o_item pointerValue] inNode:
1418                         p_playlist->p_ml_category checkItemExistence: NO]) )
1419         {
1420             vlc_object_release(p_playlist);
1421             return NO;
1422         }
1423         /* Fill the items and nodes to move in 2 different arrays */
1424         if( ((playlist_item_t *)[o_item pointerValue])->i_children > 0 )
1425             [o_nodes_array addObject: o_item];
1426         else
1427             [o_items_array addObject: o_item];
1428     }
1429
1430     /* Now we need to check if there are selected items that are in already
1431        selected nodes. In that case, we only want to move the nodes */
1432     [self removeItemsFrom: o_nodes_array ifChildrenOf: o_nodes_array];
1433     [self removeItemsFrom: o_items_array ifChildrenOf: o_nodes_array];
1434
1435     /* We add the "VLCPlaylistItemPboardType" type to be able to recognize
1436        a Drop operation coming from the playlist. */
1437
1438     [pboard declareTypes: [NSArray arrayWithObjects:
1439         @"VLCPlaylistItemPboardType", nil] owner: self];
1440     [pboard setData:[NSData data] forType:@"VLCPlaylistItemPboardType"];
1441
1442     vlc_object_release(p_playlist);
1443     return YES;
1444 }
1445
1446 - (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id <NSDraggingInfo>)info proposedItem:(id)item proposedChildIndex:(int)index
1447 {
1448     playlist_t *p_playlist = pl_Yield( VLCIntf );
1449     NSPasteboard *o_pasteboard = [info draggingPasteboard];
1450
1451     if( !p_playlist ) return NSDragOperationNone;
1452
1453     /* Dropping ON items is not allowed if item is not a node */
1454     if( item )
1455     {
1456         if( index == NSOutlineViewDropOnItemIndex &&
1457                 ((playlist_item_t *)[item pointerValue])->i_children == -1 )
1458         {
1459             vlc_object_release( p_playlist );
1460             return NSDragOperationNone;
1461         }
1462     }
1463
1464     /* Don't allow on drop on playlist root element's child */
1465     if( !item && index != NSOutlineViewDropOnItemIndex)
1466     {
1467         vlc_object_release( p_playlist );
1468         return NSDragOperationNone;
1469     }
1470
1471     /* We refuse to drop an item in anything else than a child of the General
1472        Node. We still accept items that would be root nodes of the outlineview
1473        however, to allow drop in an empty playlist. */
1474     if( !( ([self isItem: [item pointerValue] inNode: p_playlist->p_local_category checkItemExistence: NO] || 
1475         ( var_CreateGetBool( p_playlist, "media-library" ) && [self isItem: [item pointerValue] inNode: p_playlist->p_ml_category checkItemExistence: NO] ) ) || item == nil ) )
1476     {
1477         vlc_object_release( p_playlist );
1478         return NSDragOperationNone;
1479     }
1480
1481     /* Drop from the Playlist */
1482     if( [[o_pasteboard types] containsObject: @"VLCPlaylistItemPboardType"] )
1483     {
1484         unsigned int i;
1485         for( i = 0 ; i < [o_nodes_array count] ; i++ )
1486         {
1487             /* We refuse to Drop in a child of an item we are moving */
1488             if( [self isItem: [item pointerValue] inNode:
1489                     [[o_nodes_array objectAtIndex: i] pointerValue]
1490                     checkItemExistence: NO] )
1491             {
1492                 vlc_object_release( p_playlist );
1493                 return NSDragOperationNone;
1494             }
1495         }
1496         vlc_object_release( p_playlist );
1497         return NSDragOperationMove;
1498     }
1499
1500     /* Drop from the Finder */
1501     else if( [[o_pasteboard types] containsObject: NSFilenamesPboardType] )
1502     {
1503         vlc_object_release( p_playlist );
1504         return NSDragOperationGeneric;
1505     }
1506     vlc_object_release( p_playlist );
1507     return NSDragOperationNone;
1508 }
1509
1510 - (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id <NSDraggingInfo>)info item:(id)item childIndex:(int)index
1511 {
1512     playlist_t * p_playlist =  pl_Yield( VLCIntf );
1513     NSPasteboard *o_pasteboard = [info draggingPasteboard];
1514
1515     /* Drag & Drop inside the playlist */
1516     if( [[o_pasteboard types] containsObject: @"VLCPlaylistItemPboardType"] )
1517     {
1518         int i_row, i_removed_from_node = 0;
1519         unsigned int i;
1520         playlist_item_t *p_new_parent, *p_item = NULL;
1521         NSArray *o_all_items = [o_nodes_array arrayByAddingObjectsFromArray:
1522                                                                 o_items_array];
1523         /* If the item is to be dropped as root item of the outline, make it a
1524            child of the General node.
1525            Else, choose the proposed parent as parent. */
1526         if( item == nil ) p_new_parent = p_playlist->p_local_category;
1527         else p_new_parent = [item pointerValue];
1528
1529         /* Make sure the proposed parent is a node.
1530            (This should never be true) */
1531         if( p_new_parent->i_children < 0 )
1532         {
1533             vlc_object_release( p_playlist );
1534             return NO;
1535         }
1536
1537         for( i = 0; i < [o_all_items count]; i++ )
1538         {
1539             playlist_item_t *p_old_parent = NULL;
1540             int i_old_index = 0;
1541
1542             p_item = [[o_all_items objectAtIndex:i] pointerValue];
1543             p_old_parent = p_item->p_parent;
1544             if( !p_old_parent )
1545             continue;
1546             /* We may need the old index later */
1547             if( p_new_parent == p_old_parent )
1548             {
1549                 int j;
1550                 for( j = 0; j < p_old_parent->i_children; j++ )
1551                 {
1552                     if( p_old_parent->pp_children[j] == p_item )
1553                     {
1554                         i_old_index = j;
1555                         break;
1556                     }
1557                 }
1558             }
1559
1560             vlc_mutex_lock( &p_playlist->object_lock );
1561             // Acually detach the item from the old position
1562             if( playlist_NodeRemoveItem( p_playlist, p_item, p_old_parent ) ==
1563                 VLC_SUCCESS )
1564             {
1565                 int i_new_index;
1566                 /* Calculate the new index */
1567                 if( index == -1 )
1568                 i_new_index = -1;
1569                 /* If we move the item in the same node, we need to take into
1570                    account that one item will be deleted */
1571                 else
1572                 {
1573                     if ((p_new_parent == p_old_parent &&
1574                                    i_old_index < index + (int)i) )
1575                     {
1576                         i_removed_from_node++;
1577                     }
1578                     i_new_index = index + i - i_removed_from_node;
1579                 }
1580                 // Reattach the item to the new position
1581                 playlist_NodeInsert( p_playlist, p_item, p_new_parent, i_new_index );
1582             }
1583             vlc_mutex_unlock( &p_playlist->object_lock );
1584         }
1585         [self playlistUpdated];
1586         i_row = [o_outline_view rowForItem:[o_outline_dict
1587             objectForKey:[NSString stringWithFormat: @"%p",
1588             [[o_all_items objectAtIndex: 0] pointerValue]]]];
1589
1590         if( i_row == -1 )
1591         {
1592             i_row = [o_outline_view rowForItem:[o_outline_dict
1593             objectForKey:[NSString stringWithFormat: @"%p", p_new_parent]]];
1594         }
1595
1596         [o_outline_view deselectAll: self];
1597         [o_outline_view selectRow: i_row byExtendingSelection: NO];
1598         [o_outline_view scrollRowToVisible: i_row];
1599
1600         vlc_object_release( p_playlist );
1601         return YES;
1602     }
1603
1604     else if( [[o_pasteboard types] containsObject: NSFilenamesPboardType] )
1605     {
1606         int i;
1607         playlist_item_t *p_node = [item pointerValue];
1608
1609         NSArray *o_array = [NSArray array];
1610         NSArray *o_values = [[o_pasteboard propertyListForType:
1611                                         NSFilenamesPboardType]
1612                                 sortedArrayUsingSelector:
1613                                         @selector(caseInsensitiveCompare:)];
1614
1615         for( i = 0; i < (int)[o_values count]; i++)
1616         {
1617             NSDictionary *o_dic;
1618             o_dic = [NSDictionary dictionaryWithObject:[o_values
1619                         objectAtIndex:i] forKey:@"ITEM_URL"];
1620             o_array = [o_array arrayByAddingObject: o_dic];
1621         }
1622
1623         if ( item == nil )
1624         {
1625             [self appendArray: o_array atPos: index enqueue: YES];
1626         }
1627         /* This should never occur */
1628         else if( p_node->i_children == -1 )
1629         {
1630             vlc_object_release( p_playlist );
1631             return NO;
1632         }
1633         else
1634         {
1635             [self appendNodeArray: o_array inNode: p_node
1636                 atPos: index enqueue: YES];
1637         }
1638         vlc_object_release( p_playlist );
1639         return YES;
1640     }
1641     vlc_object_release( p_playlist );
1642     return NO;
1643 }
1644 @end
1645
1646