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