9ee7912296720df01bef6599e29fa1d2f2845129
[vlc.git] / 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
120     vlc_object_release( p_playlist );
121     [self initStrings];
122 }
123
124 - (void)initStrings
125 {
126     [[o_tc_name headerCell] setStringValue:_NS("Name")];
127     [[o_tc_author headerCell] setStringValue:_NS("Author")];
128     [[o_tc_duration headerCell] setStringValue:_NS("Duration")];
129 }
130
131 - (NSOutlineView *)outlineView
132 {
133     return o_outline_view;
134 }
135
136 - (playlist_item_t *)selectedPlaylistItem
137 {
138     return [[o_outline_view itemAtRow: [o_outline_view selectedRow]]
139                                                                 pointerValue];
140 }
141
142 @end
143
144 @implementation VLCPlaylistCommon (NSOutlineViewDataSource)
145
146 /* return the number of children for Obj-C pointer item */ /* DONE */
147 - (int)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
148 {
149     int i_return = 0;
150     playlist_item_t *p_item = NULL;
151     playlist_t * p_playlist = pl_Hold( VLCIntf );
152     assert( outlineView == o_outline_view );
153
154     if( !item )
155         p_item = p_playlist->p_root_category;
156     else
157         p_item = (playlist_item_t *)[item pointerValue];
158
159     if( p_item )
160         i_return = p_item->i_children;
161
162     pl_Release( VLCIntf );
163
164     return i_return > 0 ? i_return : 0;
165 }
166
167 /* return the child at index for the Obj-C pointer item */ /* DONE */
168 - (id)outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item
169 {
170     playlist_item_t *p_return = NULL, *p_item = NULL;
171     NSValue *o_value;
172     playlist_t * p_playlist = pl_Hold( VLCIntf );
173
174     PL_LOCK;
175     if( item == nil )
176     {
177         /* root object */
178         p_item = p_playlist->p_root_category;
179     }
180     else
181     {
182         p_item = (playlist_item_t *)[item pointerValue];
183     }
184     if( p_item && index < p_item->i_children && index >= 0 )
185         p_return = p_item->pp_children[index];
186     PL_UNLOCK;
187
188     vlc_object_release( p_playlist );
189
190     o_value = [o_outline_dict objectForKey:[NSString stringWithFormat: @"%p", p_return]];
191
192     if( o_value == nil )
193     {
194         /* Why is there a warning if that happens all the time and seems
195          * to be normal? Add an assert and fix it. 
196          * msg_Warn( VLCIntf, "playlist item misses pointer value, adding one" ); */
197         o_value = [[NSValue valueWithPointer: p_return] retain];
198     }
199     return o_value;
200 }
201
202 /* is the item expandable */
203 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
204 {
205     int i_return = 0;
206     playlist_t *p_playlist = pl_Hold( VLCIntf );
207
208     if( item == nil )
209     {
210         /* root object */
211         if( p_playlist->p_root_category )
212         {
213             i_return = p_playlist->p_root_category->i_children;
214         }
215     }
216     else
217     {
218         playlist_item_t *p_item = (playlist_item_t *)[item pointerValue];
219         if( p_item )
220             i_return = p_item->i_children;
221     }
222     pl_Release( VLCIntf );
223
224     return (i_return >= 0);
225 }
226
227 /* retrieve the string values for the cells */
228 - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)o_tc byItem:(id)item
229 {
230     id o_value = nil;
231     playlist_item_t *p_item;
232
233     /* For error handling */
234     static BOOL attempted_reload = NO;
235
236     if( item == nil || ![item isKindOfClass: [NSValue class]] )
237     {
238         /* Attempt to fix the error by asking for a data redisplay
239          * This might cause infinite loop, so add a small check */
240         if( !attempted_reload )
241         {
242             attempted_reload = YES;
243             [outlineView reloadData];
244         }
245         return @"error" ;
246     }
247  
248     p_item = (playlist_item_t *)[item pointerValue];
249     if( !p_item || !p_item->p_input )
250     {
251         /* Attempt to fix the error by asking for a data redisplay
252          * This might cause infinite loop, so add a small check */
253         if( !attempted_reload )
254         {
255             attempted_reload = YES;
256             [outlineView reloadData];
257         }
258         return @"error";
259     }
260  
261     attempted_reload = NO;
262
263     if( [[o_tc identifier] isEqualToString:@"name"] )
264     {
265         /* sanity check to prevent the NSString class from crashing */
266         char *psz_title =  input_item_GetTitle( p_item->p_input );
267         if( !EMPTY_STR( psz_title ) )
268         {
269             o_value = [NSString stringWithUTF8String: psz_title];
270         }
271         else
272         {
273             char *psz_name = input_item_GetName( p_item->p_input );
274             if( psz_name )
275                 o_value = [NSString stringWithUTF8String: psz_name];
276             free( psz_name );
277         }
278         free( psz_title );
279     }
280     else if( [[o_tc identifier] isEqualToString:@"artist"] )
281     {
282         char *psz_artist = input_item_GetArtist( p_item->p_input );
283         if( psz_artist )
284             o_value = [NSString stringWithUTF8String: psz_artist];
285         free( psz_artist );
286     }
287     else if( [[o_tc identifier] isEqualToString:@"duration"] )
288     {
289         char psz_duration[MSTRTIME_MAX_SIZE];
290         mtime_t dur = input_item_GetDuration( p_item->p_input );
291         if( dur != -1 )
292         {
293             secstotimestr( psz_duration, dur/1000000 );
294             o_value = [NSString stringWithUTF8String: psz_duration];
295         }
296         else
297             o_value = @"--:--";
298     }
299     else if( [[o_tc identifier] isEqualToString:@"status"] )
300     {
301         if( input_item_HasErrorWhenReading( p_item->p_input ) )
302         {
303             o_value = [NSImage imageWithWarningIcon];
304         }
305     }
306     return o_value;
307 }
308
309 @end
310
311 /*****************************************************************************
312  * VLCPlaylistWizard implementation
313  *****************************************************************************/
314 @implementation VLCPlaylistWizard
315
316 - (IBAction)reloadOutlineView
317 {
318     /* Only reload the outlineview if the wizard window is open since this can
319        be quite long on big playlists */
320     if( [[o_outline_view window] isVisible] )
321     {
322         [o_outline_view reloadData];
323     }
324 }
325
326 @end
327
328 /*****************************************************************************
329  * extension to NSOutlineView's interface to fix compilation warnings
330  * and let us access these 2 functions properly
331  * this uses a private Apple-API, but works fine on all current OSX releases
332  * keep checking for compatiblity with future releases though
333  *****************************************************************************/
334
335 @interface NSOutlineView (UndocumentedSortImages)
336 + (NSImage *)_defaultTableHeaderSortImage;
337 + (NSImage *)_defaultTableHeaderReverseSortImage;
338 @end
339
340
341 /*****************************************************************************
342  * VLCPlaylist implementation
343  *****************************************************************************/
344 @implementation VLCPlaylist
345
346 - (id)init
347 {
348     self = [super init];
349     if ( self != nil )
350     {
351         o_nodes_array = [[NSMutableArray alloc] init];
352         o_items_array = [[NSMutableArray alloc] init];
353     }
354     return self;
355 }
356
357 - (void)dealloc
358 {
359     [o_nodes_array release];
360     [o_items_array release];
361     [super dealloc];
362 }
363
364 - (void)awakeFromNib
365 {
366     playlist_t * p_playlist = pl_Hold( VLCIntf );
367
368     int i;
369
370     [super awakeFromNib];
371
372     [o_outline_view setDoubleAction: @selector(playItem:)];
373
374     [o_outline_view registerForDraggedTypes:
375         [NSArray arrayWithObjects: NSFilenamesPboardType,
376         @"VLCPlaylistItemPboardType", nil]];
377     [o_outline_view setIntercellSpacing: NSMakeSize (0.0, 1.0)];
378
379     /* This uses private Apple API which works fine until 10.5.
380      * We need to keep checking in the future!
381      * These methods are being added artificially to NSOutlineView's interface above */
382     o_ascendingSortingImage = [[NSOutlineView class] _defaultTableHeaderSortImage];
383     o_descendingSortingImage = [[NSOutlineView class] _defaultTableHeaderReverseSortImage];
384
385     o_tc_sortColumn = nil;
386
387     char ** ppsz_name;
388     char ** ppsz_services = services_discovery_GetServicesNames( p_playlist, &ppsz_name );
389     if( !ppsz_services )
390     {
391         vlc_object_release( p_playlist );
392         return;
393     }
394     
395     for( i = 0; ppsz_services[i]; i++ )
396     {
397         bool  b_enabled;
398         NSMenuItem  *o_lmi;
399
400         char * name = ppsz_name[i] ? ppsz_name[i] : ppsz_services[i];
401         /* Check whether to enable these menuitems */
402         b_enabled = playlist_IsServicesDiscoveryLoaded( p_playlist, ppsz_services[i] );
403
404         /* Create the menu entries used in the playlist menu */
405         o_lmi = [[o_mi_services submenu] addItemWithTitle:
406                  [NSString stringWithUTF8String: name]
407                                          action: @selector(servicesChange:)
408                                          keyEquivalent: @""];
409         [o_lmi setTarget: self];
410         [o_lmi setRepresentedObject: [NSString stringWithUTF8String: ppsz_services[i]]];
411         if( b_enabled ) [o_lmi setState: NSOnState];
412
413         /* Create the menu entries for the main menu */
414         o_lmi = [[o_mm_mi_services submenu] addItemWithTitle:
415                  [NSString stringWithUTF8String: name]
416                                          action: @selector(servicesChange:)
417                                          keyEquivalent: @""];
418         [o_lmi setTarget: self];
419         [o_lmi setRepresentedObject: [NSString stringWithUTF8String: ppsz_services[i]]];
420         if( b_enabled ) [o_lmi setState: NSOnState];
421
422         free( ppsz_services[i] );
423         free( ppsz_name[i] );
424     }
425     free( ppsz_services );
426     free( ppsz_name );
427
428     vlc_object_release( p_playlist );
429 }
430
431 - (void)searchfieldChanged:(NSNotification *)o_notification
432 {
433     [o_search_field setStringValue:[[o_notification object] stringValue]];
434 }
435
436 - (void)initStrings
437 {
438     [super initStrings];
439
440     [o_mi_save_playlist setTitle: _NS("Save Playlist...")];
441     [o_mi_play setTitle: _NS("Play")];
442     [o_mi_delete setTitle: _NS("Delete")];
443     [o_mi_recursive_expand setTitle: _NS("Expand Node")];
444     [o_mi_selectall setTitle: _NS("Select All")];
445     [o_mi_info setTitle: _NS("Information...")];
446     [o_mi_preparse setTitle: _NS("Fetch Meta Data")];
447     [o_mi_revealInFinder setTitle: _NS("Reveal in Finder")];
448     [o_mm_mi_revealInFinder setTitle: _NS("Reveal in Finder")];
449     [[o_mm_mi_revealInFinder menu] setAutoenablesItems: NO];
450     [o_mi_sort_name setTitle: _NS("Sort Node by Name")];
451     [o_mi_sort_author setTitle: _NS("Sort Node by Author")];
452     [o_mi_services setTitle: _NS("Services discovery")];
453     [o_mm_mi_services setTitle: _NS("Services discovery")];
454     [o_status_field setStringValue: _NS("No items in the playlist")];
455
456     [o_search_field setToolTip: _NS("Search in Playlist")];
457     [o_mi_addNode setTitle: _NS("Add Folder to Playlist")];
458
459     [o_save_accessory_text setStringValue: _NS("File Format:")];
460     [[o_save_accessory_popup itemAtIndex:0] setTitle: _NS("Extended M3U")];
461     [[o_save_accessory_popup itemAtIndex:1] setTitle: _NS("XML Shareable Playlist Format (XSPF)")];
462 }
463
464 - (void)playlistUpdated
465 {
466     /* Clear indications of any existing column sorting */
467     for( unsigned int i = 0 ; i < [[o_outline_view tableColumns] count] ; i++ )
468     {
469         [o_outline_view setIndicatorImage:nil inTableColumn:
470                             [[o_outline_view tableColumns] objectAtIndex:i]];
471     }
472
473     [o_outline_view setHighlightedTableColumn:nil];
474     o_tc_sortColumn = nil;
475     // TODO Find a way to keep the dict size to a minimum
476     //[o_outline_dict removeAllObjects];
477     [o_outline_view reloadData];
478     [[[[VLCMain sharedInstance] getWizard] getPlaylistWizard] reloadOutlineView];
479     [[[[VLCMain sharedInstance] getBookmarks] getDataTable] reloadData];
480
481     playlist_t *p_playlist = pl_Hold( VLCIntf );
482
483     if( playlist_CurrentSize( p_playlist ) >= 2 )
484     {
485         [o_status_field setStringValue: [NSString stringWithFormat:
486                     _NS("%i items"),
487                 playlist_CurrentSize( p_playlist )]];
488     }
489     else
490     {
491         if( playlist_IsEmpty( p_playlist ) )
492             [o_status_field setStringValue: _NS("No items in the playlist")];
493         else
494             [o_status_field setStringValue: _NS("1 item")];
495     }
496     vlc_object_release( p_playlist );
497
498     [self outlineViewSelectionDidChange: nil];
499 }
500
501 - (void)playModeUpdated
502 {
503     playlist_t *p_playlist = pl_Hold( VLCIntf );
504
505     bool loop = var_GetBool( p_playlist, "loop" );
506     bool repeat = var_GetBool( p_playlist, "repeat" );
507     if( repeat )
508         [[[VLCMain sharedInstance] getControls] repeatOne];
509     else if( loop )
510         [[[VLCMain sharedInstance] getControls] repeatAll];
511     else
512         [[[VLCMain sharedInstance] getControls] repeatOff];
513
514     [[[VLCMain sharedInstance] getControls] shuffle];
515
516     vlc_object_release( p_playlist );
517 }
518
519 - (void)outlineViewSelectionDidChange:(NSNotification *)notification
520 {
521     // FIXME: unsafe
522     playlist_item_t * p_item = [[o_outline_view itemAtRow:[o_outline_view selectedRow]] pointerValue];
523
524     if( p_item )
525     {
526         /* update our info-panel to reflect the new item */
527         [[[VLCMain sharedInstance] getInfo] updatePanelWithItem:p_item->p_input];
528         
529         /* update the state of our Reveal-in-Finder menu items */
530         NSMutableString *o_mrl;
531         char *psz_uri = input_item_GetURI( p_item->p_input );
532         if( psz_uri )
533         {
534             o_mrl = [NSMutableString stringWithUTF8String: psz_uri];
535         
536             /* perform some checks whether it is a file and if it is local at all... */
537             NSRange prefix_range = [o_mrl rangeOfString: @"file:"];
538             if( prefix_range.location != NSNotFound )
539                 [o_mrl deleteCharactersInRange: prefix_range];
540             
541             if( [o_mrl characterAtIndex:0] == '/' )
542             {
543                 [o_mi_revealInFinder setEnabled: YES];
544                 [o_mm_mi_revealInFinder setEnabled: YES];
545                 return;
546             }
547         }
548         [o_mi_revealInFinder setEnabled: NO];
549         [o_mm_mi_revealInFinder setEnabled: NO];
550     }
551 }
552
553 - (BOOL)isSelectionEmpty
554 {
555     return [o_outline_view selectedRow] == -1;
556 }
557
558 - (void)updateRowSelection
559 {
560     int i_row;
561     unsigned int j;
562
563     // FIXME: unsafe
564     playlist_t *p_playlist = pl_Hold( VLCIntf );
565     playlist_item_t *p_item, *p_temp_item;
566     NSMutableArray *o_array = [NSMutableArray array];
567
568     p_item = p_playlist->status.p_item;
569     if( p_item == NULL )
570     {
571         vlc_object_release(p_playlist);
572         return;
573     }
574
575     p_temp_item = p_item;
576     while( p_temp_item->p_parent )
577     {
578         [o_array insertObject: [NSValue valueWithPointer: p_temp_item] atIndex: 0];
579         p_temp_item = p_temp_item->p_parent;
580     }
581
582     for( j = 0; j < [o_array count] - 1; j++ )
583     {
584         id o_item;
585         if( ( o_item = [o_outline_dict objectForKey:
586                             [NSString stringWithFormat: @"%p",
587                             [[o_array objectAtIndex:j] pointerValue]]] ) != nil )
588         {
589             [o_outline_view expandItem: o_item];
590         }
591
592     }
593
594     vlc_object_release( p_playlist );
595
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( p_playlist->status.i_status != PLAYLIST_STOPPED &&
908                 [self isItem: p_playlist->status.p_item 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",  p_playlist->status.p_item]];
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