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