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