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