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