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