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