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