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