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