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