]> git.sesse.net Git - vlc/blob - modules/gui/macosx/playlist.m
dash: MPDManager: Fixing memory leak
[vlc] / modules / gui / macosx / playlist.m
1 /*****************************************************************************
2  * playlist.m: MacOS X interface module
3  *****************************************************************************
4 * Copyright (C) 2002-2009 VLC authors and VideoLAN
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     NSUInteger count = [[o_outline_view tableColumns] count];
506     for( NSUInteger i = 0 ; i < count ; i++ )
507     {
508         [o_outline_view setIndicatorImage:nil inTableColumn:
509                             [[o_outline_view tableColumns] objectAtIndex:i]];
510     }
511
512     [o_outline_view setHighlightedTableColumn:nil];
513     o_tc_sortColumn = nil;
514     // TODO Find a way to keep the dict size to a minimum
515     //[o_outline_dict removeAllObjects];
516     [o_outline_view reloadData];
517     [[[[VLCMain sharedInstance] wizard] playlistWizard] reloadOutlineView];
518     [[[[VLCMain sharedInstance] bookmarks] dataTable] reloadData];
519
520     [self outlineViewSelectionDidChange: nil];
521 }
522
523 - (void)outlineViewSelectionDidChange:(NSNotification *)notification
524 {
525     // FIXME: unsafe
526     playlist_item_t * p_item = [[o_outline_view itemAtRow:[o_outline_view selectedRow]] pointerValue];
527
528     if( p_item )
529     {
530         /* update the state of our Reveal-in-Finder menu items */
531         NSMutableString *o_mrl;
532         char *psz_uri = input_item_GetURI( p_item->p_input );
533
534         [o_mi_revealInFinder setEnabled: NO];
535         [o_mm_mi_revealInFinder setEnabled: NO];
536         if( psz_uri )
537         {
538             o_mrl = [NSMutableString stringWithUTF8String: psz_uri];
539
540             /* perform some checks whether it is a file and if it is local at all... */
541             NSRange prefix_range = [o_mrl rangeOfString: @"file:"];
542             if( prefix_range.location != NSNotFound )
543                 [o_mrl deleteCharactersInRange: prefix_range];
544
545             if( [o_mrl characterAtIndex:0] == '/' )
546             {
547                 [o_mi_revealInFinder setEnabled: YES];
548                 [o_mm_mi_revealInFinder setEnabled: YES];
549             }
550             free( psz_uri );
551         }
552
553         /* update our info-panel to reflect the new item */
554         [[[VLCMain sharedInstance] info] updatePanelWithItem:p_item->p_input];
555     }
556 }
557
558 - (BOOL)isSelectionEmpty
559 {
560     return [o_outline_view selectedRow] == -1;
561 }
562
563 - (void)updateRowSelection
564 {
565     // FIXME: unsafe
566     playlist_t *p_playlist = pl_Get( VLCIntf );
567     playlist_item_t *p_item, *p_temp_item;
568     NSMutableArray *o_array = [NSMutableArray array];
569
570     PL_LOCK;
571     p_item = playlist_CurrentPlayingItem( p_playlist );
572     if( p_item == NULL )
573     {
574         PL_UNLOCK;
575         return;
576     }
577
578     p_temp_item = p_item;
579     while( p_temp_item->p_parent )
580     {
581         [o_array insertObject: [NSValue valueWithPointer: p_temp_item] atIndex: 0];
582         p_temp_item = p_temp_item->p_parent;
583     }
584     PL_UNLOCK;
585
586     NSUInteger count = [o_array count];
587     for( NSUInteger j = 0; j < count - 1; j++ )
588     {
589         id o_item;
590         if( ( o_item = [o_outline_dict objectForKey:
591                             [NSString stringWithFormat: @"%p",
592                             [[o_array objectAtIndex:j] pointerValue]]] ) != nil )
593         {
594             [o_outline_view expandItem: o_item];
595         }
596     }
597 }
598
599 /* Check if p_item is a child of p_node recursively. We need to check the item
600    existence first since OSX sometimes tries to redraw items that have been
601    deleted. We don't do it when not required since this verification takes
602    quite a long time on big playlists (yes, pretty hacky). */
603
604 - (BOOL)isItem: (playlist_item_t *)p_item
605                     inNode: (playlist_item_t *)p_node
606                     checkItemExistence:(BOOL)b_check
607                     locked:(BOOL)b_locked
608
609 {
610     playlist_t * p_playlist = pl_Get( VLCIntf );
611     playlist_item_t *p_temp_item = p_item;
612
613     if( p_node == p_item )
614         return YES;
615
616     if( p_node->i_children < 1)
617         return NO;
618
619     if ( p_temp_item )
620     {
621         int i;
622         if(!b_locked) PL_LOCK;
623
624         if( b_check )
625         {
626         /* Since outlineView: willDisplayCell:... may call this function with
627            p_items that don't exist anymore, first check if the item is still
628            in the playlist. Any cleaner solution welcomed. */
629             for( i = 0; i < p_playlist->all_items.i_size; i++ )
630             {
631                 if( ARRAY_VAL( p_playlist->all_items, i) == p_item ) break;
632                 else if ( i == p_playlist->all_items.i_size - 1 )
633                 {
634                     if(!b_locked) PL_UNLOCK;
635                     return NO;
636                 }
637             }
638         }
639
640         while( p_temp_item )
641         {
642             p_temp_item = p_temp_item->p_parent;
643             if( p_temp_item == p_node )
644             {
645                 if(!b_locked) PL_UNLOCK;
646                 return YES;
647             }
648         }
649         if(!b_locked) PL_UNLOCK;
650     }
651     return NO;
652 }
653
654 - (BOOL)isItem: (playlist_item_t *)p_item
655                     inNode: (playlist_item_t *)p_node
656                     checkItemExistence:(BOOL)b_check
657 {
658     return [self isItem:p_item inNode:p_node checkItemExistence:b_check locked:NO];
659 }
660
661 /* This method is useful for instance to remove the selected children of an
662    already selected node */
663 - (void)removeItemsFrom:(id)o_items ifChildrenOf:(id)o_nodes
664 {
665     NSUInteger itemCount = [o_items count];
666     NSUInteger nodeCount = [o_nodes count];
667     for( NSUInteger i = 0 ; i < itemCount ; i++ )
668     {
669         for ( NSUInteger j = 0 ; j < nodeCount ; 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     NSIndexSet *o_selected_indexes;
829     intf_thread_t * p_intf = VLCIntf;
830     playlist_t * p_playlist = pl_Get( p_intf );
831     playlist_item_t *p_item = NULL;
832
833     o_selected_indexes = [o_outline_view selectedRowIndexes];
834     i_count = [o_selected_indexes count];
835
836     NSUInteger indexes[i_count];
837     [o_selected_indexes getIndexes:indexes maxCount:i_count inIndexRange:nil];
838     for (int i = 0; i < i_count; i++)
839     {
840         p_item = [[o_outline_view itemAtRow:indexes[i]] pointerValue];
841         [o_outline_view deselectRow: indexes[i]];
842
843         if( p_item )
844         {
845             if( p_item->i_children == -1 )
846                 playlist_PreparseEnqueue( p_playlist, p_item->p_input );
847             else
848                 msg_Dbg( p_intf, "preparsing nodes not implemented" );
849         }
850     }
851     [self playlistUpdated];
852 }
853
854 - (IBAction)downloadCoverArt:(id)sender
855 {
856     int i_count;
857     NSIndexSet *o_selected_indexes;
858     intf_thread_t * p_intf = VLCIntf;
859     playlist_t * p_playlist = pl_Get( p_intf );
860     playlist_item_t *p_item = NULL;
861
862     o_selected_indexes = [o_outline_view selectedRowIndexes];
863     i_count = [o_selected_indexes count];
864
865     NSUInteger indexes[i_count];
866     [o_selected_indexes getIndexes:indexes maxCount:i_count inIndexRange:nil];
867     for (int i = 0; i < i_count; i++)
868     {
869         p_item = [[o_outline_view itemAtRow: indexes[i]] pointerValue];   
870         [o_outline_view deselectRow: indexes[i]];
871
872         if( p_item && p_item->i_children == -1 )
873             playlist_AskForArtEnqueue( p_playlist, p_item->p_input );
874     }
875     [self playlistUpdated];
876 }
877
878 - (IBAction)servicesChange:(id)sender
879 {
880     NSMenuItem *o_mi = (NSMenuItem *)sender;
881     NSString *o_string = [o_mi representedObject];
882     playlist_t * p_playlist = pl_Get( VLCIntf );
883     if( !playlist_IsServicesDiscoveryLoaded( p_playlist, [o_string UTF8String] ) )
884         playlist_ServicesDiscoveryAdd( p_playlist, [o_string UTF8String] );
885     else
886         playlist_ServicesDiscoveryRemove( p_playlist, [o_string UTF8String] );
887
888     [o_mi setState: playlist_IsServicesDiscoveryLoaded( p_playlist,
889                                           [o_string UTF8String] ) ? YES : NO];
890
891     [self playlistUpdated];
892     return;
893 }
894
895 - (IBAction)selectAll:(id)sender
896 {
897     [o_outline_view selectAll: nil];
898 }
899
900 - (IBAction)deleteItem:(id)sender
901 {
902     int i_count;
903     NSIndexSet *o_selected_indexes;
904     playlist_t * p_playlist;
905     intf_thread_t * p_intf = VLCIntf;
906
907     o_selected_indexes = [o_outline_view selectedRowIndexes];
908     i_count = [o_selected_indexes count];
909
910     p_playlist = pl_Get( p_intf );
911
912     NSUInteger indexes[i_count];
913     [o_selected_indexes getIndexes:indexes maxCount:i_count inIndexRange:nil];
914     for (int i = 0; i < i_count; i++)
915     {
916         id o_item = [o_outline_view itemAtRow: indexes[i]];
917         [o_outline_view deselectRow: indexes[i]];
918
919         PL_LOCK;
920         playlist_item_t *p_item = [o_item pointerValue];
921 #ifndef NDEBUG
922         msg_Dbg( p_intf, "deleting item %i (of %i) with id \"%i\", pointerValue \"%p\" and %i children", i+1, i_count,
923                 p_item->p_input->i_id, [o_item pointerValue], p_item->i_children +1 );
924 #endif
925
926         if( p_item->i_children != -1 )
927         //is a node and not an item
928         {
929             if( playlist_Status( p_playlist ) != PLAYLIST_STOPPED &&
930                 [self isItem: playlist_CurrentPlayingItem( p_playlist ) inNode: ((playlist_item_t *)[o_item pointerValue])
931                         checkItemExistence: NO locked:YES] == YES )
932                 // if current item is in selected node and is playing then stop playlist
933                 playlist_Control(p_playlist, PLAYLIST_STOP, pl_Locked );
934
935             playlist_NodeDelete( p_playlist, p_item, true, false );
936         }
937         else
938             playlist_DeleteFromInput( p_playlist, p_item->p_input, pl_Locked );
939
940         PL_UNLOCK;
941         [o_outline_dict removeObjectForKey:[NSString stringWithFormat:@"%p", [o_item pointerValue]]];
942         [o_item release];
943     }
944
945     [self playlistUpdated];
946 }
947
948 - (IBAction)sortNodeByName:(id)sender
949 {
950     [self sortNode: SORT_TITLE];
951 }
952
953 - (IBAction)sortNodeByAuthor:(id)sender
954 {
955     [self sortNode: SORT_ARTIST];
956 }
957
958 - (void)sortNode:(int)i_mode
959 {
960     playlist_t * p_playlist = pl_Get( VLCIntf );
961     playlist_item_t * p_item;
962
963     if( [o_outline_view selectedRow] > -1 )
964     {
965         p_item = [[o_outline_view itemAtRow: [o_outline_view selectedRow]] pointerValue];
966     }
967     else
968     /*If no item is selected, sort the whole playlist*/
969     {
970         p_item = p_playlist->p_root_category;
971     }
972
973     PL_LOCK;
974     if( p_item->i_children > -1 ) // the item is a node
975     {
976         playlist_RecursiveNodeSort( p_playlist, p_item, i_mode, ORDER_NORMAL );
977     }
978     else
979     {
980         playlist_RecursiveNodeSort( p_playlist,
981                 p_item->p_parent, i_mode, ORDER_NORMAL );
982     }
983     PL_UNLOCK;
984     [self playlistUpdated];
985 }
986
987 - (input_item_t *)createItem:(NSDictionary *)o_one_item
988 {
989     intf_thread_t * p_intf = VLCIntf;
990     playlist_t * p_playlist = pl_Get( p_intf );
991
992     input_item_t *p_input;
993     BOOL b_rem = FALSE, b_dir = FALSE;
994     NSString *o_uri, *o_name;
995     NSArray *o_options;
996     NSURL *o_true_file;
997
998     /* Get the item */
999     o_uri = (NSString *)[o_one_item objectForKey: @"ITEM_URL"];
1000     o_name = (NSString *)[o_one_item objectForKey: @"ITEM_NAME"];
1001     o_options = (NSArray *)[o_one_item objectForKey: @"ITEM_OPTIONS"];
1002
1003     /* Find the name for a disc entry (i know, can you believe the trouble?) */
1004     if( ( !o_name || [o_name isEqualToString:@""] ) && [o_uri rangeOfString: @"/dev/"].location != NSNotFound )
1005     {
1006         int i_count;
1007         struct statfs *mounts = NULL;
1008
1009         i_count = getmntinfo (&mounts, MNT_NOWAIT);
1010         /* getmntinfo returns a pointer to static data. Do not free. */
1011         for( int i_index = 0 ; i_index < i_count; i_index++ )
1012         {
1013             NSMutableString *o_temp, *o_temp2;
1014             o_temp = [NSMutableString stringWithString: o_uri];
1015             o_temp2 = [NSMutableString stringWithUTF8String: mounts[i_index].f_mntfromname];
1016             [o_temp replaceOccurrencesOfString: @"/dev/rdisk" withString: @"/dev/disk" options:NSLiteralSearch range:NSMakeRange(0, [o_temp length]) ];
1017             [o_temp2 replaceOccurrencesOfString: @"s0" withString: @"" options:NSLiteralSearch range:NSMakeRange(0, [o_temp2 length]) ];
1018             [o_temp2 replaceOccurrencesOfString: @"s1" withString: @"" options:NSLiteralSearch range:NSMakeRange(0, [o_temp2 length]) ];
1019
1020             if( strstr( [o_temp fileSystemRepresentation], [o_temp2 fileSystemRepresentation] ) != NULL )
1021             {
1022                 o_name = [[NSFileManager defaultManager] displayNameAtPath: [NSString stringWithUTF8String:mounts[i_index].f_mntonname]];
1023             }
1024         }
1025     }
1026
1027     if( [[NSFileManager defaultManager] fileExistsAtPath:o_uri isDirectory:&b_dir] && b_dir &&
1028         [[NSWorkspace sharedWorkspace] getFileSystemInfoForPath: o_uri isRemovable: &b_rem
1029                 isWritable:NULL isUnmountable:NULL description:NULL type:NULL] && b_rem   )
1030     {
1031         /* All of this is to make sure CD's play when you D&D them on VLC */
1032         /* Converts mountpoint to a /dev file */
1033         struct statfs *buf;
1034         char *psz_dev;
1035         NSMutableString *o_temp;
1036
1037         buf = (struct statfs *) malloc (sizeof(struct statfs));
1038         statfs( [o_uri fileSystemRepresentation], buf );
1039         psz_dev = strdup(buf->f_mntfromname);
1040         o_temp = [NSMutableString stringWithUTF8String: psz_dev ];
1041         [o_temp replaceOccurrencesOfString: @"/dev/disk" withString: @"/dev/rdisk" options:NSLiteralSearch range:NSMakeRange(0, [o_temp length]) ];
1042         [o_temp replaceOccurrencesOfString: @"s0" withString: @"" options:NSLiteralSearch range:NSMakeRange(0, [o_temp length]) ];
1043         [o_temp replaceOccurrencesOfString: @"s1" withString: @"" options:NSLiteralSearch range:NSMakeRange(0, [o_temp length]) ];
1044         o_uri = o_temp;
1045     }
1046
1047     p_input = input_item_New( [o_uri fileSystemRepresentation], o_name ? [o_name UTF8String] : NULL );
1048     if( !p_input )
1049         return NULL;
1050
1051     if( o_options )
1052     {
1053         NSUInteger count = [o_options count];
1054         for( NSUInteger i = 0; i < count; i++ )
1055         {
1056             input_item_AddOption( p_input, [[o_options objectAtIndex:i] UTF8String],
1057                                   VLC_INPUT_OPTION_TRUSTED );
1058         }
1059     }
1060
1061     /* Recent documents menu */
1062     o_true_file = [NSURL URLWithString: o_uri];
1063     if( o_true_file != nil && (BOOL)config_GetInt( p_playlist, "macosx-recentitems" ) == YES )
1064     {
1065         [[NSDocumentController sharedDocumentController]
1066             noteNewRecentDocumentURL: o_true_file];
1067     }
1068     return p_input;
1069 }
1070
1071 - (void)appendArray:(NSArray*)o_array atPos:(int)i_position enqueue:(BOOL)b_enqueue
1072 {
1073     playlist_t * p_playlist = pl_Get( VLCIntf );
1074     NSUInteger count = [o_array count];
1075
1076     PL_LOCK;
1077     for( NSUInteger i_item = 0; i_item < count; i_item++ )
1078     {
1079         input_item_t *p_input;
1080         NSDictionary *o_one_item;
1081
1082         /* Get the item */
1083         o_one_item = [o_array objectAtIndex: i_item];
1084         p_input = [self createItem: o_one_item];
1085         if( !p_input )
1086         {
1087             continue;
1088         }
1089
1090         /* Add the item */
1091         /* FIXME: playlist_AddInput() can fail */
1092
1093         playlist_AddInput( p_playlist, p_input, PLAYLIST_INSERT, i_position == -1 ? PLAYLIST_END : i_position + i_item, true,
1094          pl_Locked );
1095
1096 /*        if( i_item == 0 && !b_enqueue )
1097         {
1098             playlist_item_t *p_item = NULL;
1099             playlist_item_t *p_node = NULL;
1100             p_item = playlist_ItemGetByInput( p_playlist, p_input );
1101             if( p_item )
1102             {
1103                 if( p_item->i_children == -1 )
1104                     p_node = p_item->p_parent;
1105                 else
1106                 {
1107                     p_node = p_item;
1108                     if( p_node->i_children > 0 && p_node->pp_children[0]->i_children == -1 )
1109                         p_item = p_node->pp_children[0];
1110                     else
1111                         p_item = NULL;
1112                 }
1113                 playlist_Control( p_playlist, PLAYLIST_VIEWPLAY, pl_Locked, p_node, p_item );
1114             }
1115         }*/
1116         vlc_gc_decref( p_input );
1117     }
1118     PL_UNLOCK;
1119     if( i_position == -1 )
1120         i_position = [o_outline_dict count] - 1;
1121
1122     [self playlistUpdated];
1123     [o_outline_view selectRowIndexes:[NSIndexSet indexSetWithIndex:i_position] byExtendingSelection:NO];
1124     [self playItem:nil];
1125 }
1126
1127 - (void)appendNodeArray:(NSArray*)o_array inNode:(playlist_item_t *)p_node atPos:(int)i_position enqueue:(BOOL)b_enqueue
1128 {
1129     playlist_t * p_playlist = pl_Get( VLCIntf );
1130     NSUInteger count = [o_array count];
1131
1132     for( NSUInteger i_item = 0; i_item < count; i_item++ )
1133     {
1134         input_item_t *p_input;
1135         NSDictionary *o_one_item;
1136
1137         /* Get the item */
1138         o_one_item = [o_array objectAtIndex: i_item];
1139         p_input = [self createItem: o_one_item];
1140
1141         if( !p_input ) continue;
1142
1143         /* Add the item */
1144         PL_LOCK;
1145         playlist_NodeAddInput( p_playlist, p_input, p_node,
1146                                       PLAYLIST_INSERT,
1147                                       i_position == -1 ?
1148                                       PLAYLIST_END : i_position + i_item,
1149                                       pl_Locked );
1150
1151
1152         if( i_item == 0 && !b_enqueue )
1153         {
1154             playlist_item_t *p_item;
1155             p_item = playlist_ItemGetByInput( p_playlist, p_input );
1156             playlist_Control( p_playlist, PLAYLIST_VIEWPLAY, pl_Locked, p_node, p_item );
1157         }
1158         PL_UNLOCK;
1159         vlc_gc_decref( p_input );
1160     }
1161     [self playlistUpdated];
1162 }
1163
1164 - (NSMutableArray *)subSearchItem:(playlist_item_t *)p_item
1165 {
1166     playlist_t *p_playlist = pl_Get( VLCIntf );
1167     playlist_item_t *p_selected_item;
1168     int i_selected_row;
1169
1170     i_selected_row = [o_outline_view selectedRow];
1171     if (i_selected_row < 0)
1172         i_selected_row = 0;
1173
1174     p_selected_item = (playlist_item_t *)[[o_outline_view itemAtRow: i_selected_row] pointerValue];
1175
1176     for( NSUInteger i_current = 0; i_current < p_item->i_children ; i_current++ )
1177     {
1178         char *psz_temp;
1179         NSString *o_current_name, *o_current_author;
1180
1181         PL_LOCK;
1182         o_current_name = [NSString stringWithUTF8String:
1183             p_item->pp_children[i_current]->p_input->psz_name];
1184         psz_temp = input_item_GetInfo( p_item->p_input ,
1185                    _("Meta-information"),_("Artist") );
1186         o_current_author = [NSString stringWithUTF8String: psz_temp];
1187         free( psz_temp);
1188         PL_UNLOCK;
1189
1190         if( p_selected_item == p_item->pp_children[i_current] &&
1191                     b_selected_item_met == NO )
1192         {
1193             b_selected_item_met = YES;
1194         }
1195         else if( p_selected_item == p_item->pp_children[i_current] &&
1196                     b_selected_item_met == YES )
1197         {
1198             return NULL;
1199         }
1200         else if( b_selected_item_met == YES &&
1201                     ( [o_current_name rangeOfString:[o_search_field
1202                         stringValue] options:NSCaseInsensitiveSearch].length ||
1203                       [o_current_author rangeOfString:[o_search_field
1204                         stringValue] options:NSCaseInsensitiveSearch].length ) )
1205         {
1206             /*Adds the parent items in the result array as well, so that we can
1207             expand the tree*/
1208             return [NSMutableArray arrayWithObject: [NSValue
1209                             valueWithPointer: p_item->pp_children[i_current]]];
1210         }
1211         if( p_item->pp_children[i_current]->i_children > 0 )
1212         {
1213             id o_result = [self subSearchItem:
1214                                             p_item->pp_children[i_current]];
1215             if( o_result != NULL )
1216             {
1217                 [o_result insertObject: [NSValue valueWithPointer:
1218                                 p_item->pp_children[i_current]] atIndex:0];
1219                 return o_result;
1220             }
1221         }
1222     }
1223     return NULL;
1224 }
1225
1226 - (IBAction)searchItem:(id)sender
1227 {
1228     playlist_t * p_playlist = pl_Get( VLCIntf );
1229     id o_result;
1230
1231     int i_row = -1;
1232
1233     b_selected_item_met = NO;
1234
1235         /*First, only search after the selected item:*
1236          *(b_selected_item_met = NO)                 */
1237     o_result = [self subSearchItem:p_playlist->p_root_category];
1238     if( o_result == NULL )
1239     {
1240         /* If the first search failed, search again from the beginning */
1241         o_result = [self subSearchItem:p_playlist->p_root_category];
1242     }
1243     if( o_result != NULL )
1244     {
1245         int i_start;
1246         if( [[o_result objectAtIndex: 0] pointerValue] == p_playlist->p_local_category )
1247             i_start = 1;
1248         else
1249             i_start = 0;
1250         NSUInteger count = [o_result count];
1251
1252         for( NSUInteger i = i_start ; i < count - 1 ; i++ )
1253         {
1254             [o_outline_view expandItem: [o_outline_dict objectForKey:
1255                         [NSString stringWithFormat: @"%p",
1256                         [[o_result objectAtIndex: i] pointerValue]]]];
1257         }
1258         i_row = [o_outline_view rowForItem: [o_outline_dict objectForKey:
1259                         [NSString stringWithFormat: @"%p",
1260                         [[o_result objectAtIndex: count - 1 ]
1261                         pointerValue]]]];
1262     }
1263     if( i_row > -1 )
1264     {
1265                 [o_outline_view selectRowIndexes:[NSIndexSet indexSetWithIndex:i_row] byExtendingSelection:NO];
1266         [o_outline_view scrollRowToVisible: i_row];
1267     }
1268 }
1269
1270 - (IBAction)recursiveExpandNode:(id)sender
1271 {
1272     id o_item = [o_outline_view itemAtRow: [o_outline_view selectedRow]];
1273     playlist_item_t *p_item = (playlist_item_t *)[o_item pointerValue];
1274
1275     if( ![[o_outline_view dataSource] outlineView: o_outline_view
1276                                                     isItemExpandable: o_item] )
1277     {
1278         o_item = [o_outline_dict objectForKey: [NSString stringWithFormat: @"%p", p_item->p_parent]];
1279     }
1280
1281     /* We need to collapse the node first, since OSX refuses to recursively
1282        expand an already expanded node, even if children nodes are collapsed. */
1283     [o_outline_view collapseItem: o_item collapseChildren: YES];
1284     [o_outline_view expandItem: o_item expandChildren: YES];
1285 }
1286
1287 - (NSMenu *)menuForEvent:(NSEvent *)o_event
1288 {
1289     NSPoint pt;
1290     bool b_rows;
1291     bool b_item_sel;
1292
1293     pt = [o_outline_view convertPoint: [o_event locationInWindow]
1294                                                  fromView: nil];
1295     int row = [o_outline_view rowAtPoint:pt];
1296     if( row != -1 )
1297         [o_outline_view selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO];
1298
1299     b_item_sel = ( row != -1 && [o_outline_view selectedRow] != -1 );
1300     b_rows = [o_outline_view numberOfRows] != 0;
1301
1302     [o_mi_play setEnabled: b_item_sel];
1303     [o_mi_delete setEnabled: b_item_sel];
1304     [o_mi_selectall setEnabled: b_rows];
1305     [o_mi_info setEnabled: b_item_sel];
1306     [o_mi_preparse setEnabled: b_item_sel];
1307     [o_mi_recursive_expand setEnabled: b_item_sel];
1308     [o_mi_sort_name setEnabled: b_item_sel];
1309     [o_mi_sort_author setEnabled: b_item_sel];
1310
1311     return( o_ctx_menu );
1312 }
1313
1314 - (void)outlineView: (NSOutlineView *)o_tv
1315                   didClickTableColumn:(NSTableColumn *)o_tc
1316 {
1317     int i_mode, i_type = 0;
1318     intf_thread_t *p_intf = VLCIntf;
1319
1320     playlist_t *p_playlist = pl_Get( p_intf );
1321
1322     /* Check whether the selected table column header corresponds to a
1323        sortable table column*/
1324     if( !( o_tc == o_tc_name || o_tc == o_tc_author ) )
1325     {
1326         return;
1327     }
1328
1329     if( o_tc_sortColumn == o_tc )
1330     {
1331         b_isSortDescending = !b_isSortDescending;
1332     }
1333     else
1334     {
1335         b_isSortDescending = false;
1336     }
1337
1338     if( o_tc == o_tc_name )
1339     {
1340         i_mode = SORT_TITLE;
1341     }
1342     else if( o_tc == o_tc_author )
1343     {
1344         i_mode = SORT_ARTIST;
1345     }
1346
1347     if( b_isSortDescending )
1348     {
1349         i_type = ORDER_REVERSE;
1350     }
1351     else
1352     {
1353         i_type = ORDER_NORMAL;
1354     }
1355
1356     PL_LOCK;
1357     playlist_RecursiveNodeSort( p_playlist, p_playlist->p_root_category, i_mode, i_type );
1358     PL_UNLOCK;
1359
1360     [self playlistUpdated];
1361
1362     o_tc_sortColumn = o_tc;
1363     [o_outline_view setHighlightedTableColumn:o_tc];
1364
1365     if( b_isSortDescending )
1366     {
1367         [o_outline_view setIndicatorImage:o_descendingSortingImage
1368                                                         inTableColumn:o_tc];
1369     }
1370     else
1371     {
1372         [o_outline_view setIndicatorImage:o_ascendingSortingImage
1373                                                         inTableColumn:o_tc];
1374     }
1375 }
1376
1377
1378 - (void)outlineView:(NSOutlineView *)outlineView
1379                                 willDisplayCell:(id)cell
1380                                 forTableColumn:(NSTableColumn *)tableColumn
1381                                 item:(id)item
1382 {
1383     playlist_t *p_playlist = pl_Get( VLCIntf );
1384
1385     id o_playing_item;
1386
1387     PL_LOCK;
1388     o_playing_item = [o_outline_dict objectForKey: [NSString stringWithFormat:@"%p",  playlist_CurrentPlayingItem( p_playlist )]];
1389     PL_UNLOCK;
1390
1391     if( [self isItem: [o_playing_item pointerValue] inNode:
1392                         [item pointerValue] checkItemExistence: YES]
1393                         || [o_playing_item isEqual: item] )
1394     {
1395         [cell setFont: [[NSFontManager sharedFontManager] convertFont:[cell font] toHaveTrait:NSBoldFontMask]];
1396     }
1397     else
1398     {
1399         [cell setFont: [[NSFontManager sharedFontManager] convertFont:[cell font] toNotHaveTrait:NSBoldFontMask]];
1400     }
1401 }
1402
1403 - (id)playingItem
1404 {
1405     playlist_t *p_playlist = pl_Get( VLCIntf );
1406
1407     id o_playing_item;
1408
1409     PL_LOCK;
1410     o_playing_item = [o_outline_dict objectForKey: [NSString stringWithFormat:@"%p",  playlist_CurrentPlayingItem( p_playlist )]];
1411     PL_UNLOCK;
1412
1413     return o_playing_item;
1414 }
1415 @end
1416
1417 @implementation VLCPlaylist (NSOutlineViewDataSource)
1418
1419 - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item
1420 {
1421     id o_value = [super outlineView: outlineView child: index ofItem: item];
1422
1423     [o_outline_dict setObject:o_value forKey:[NSString stringWithFormat:@"%p", [o_value pointerValue]]];
1424     return o_value;
1425 }
1426
1427 /* Required for drag & drop and reordering */
1428 - (BOOL)outlineView:(NSOutlineView *)outlineView writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pboard
1429 {
1430     playlist_t *p_playlist = pl_Get( VLCIntf );
1431
1432     /* First remove the items that were moved during the last drag & drop
1433        operation */
1434     [o_items_array removeAllObjects];
1435     [o_nodes_array removeAllObjects];
1436
1437     NSUInteger itemCount = [items count];
1438
1439     for( NSUInteger i = 0 ; i < itemCount ; i++ )
1440     {
1441         id o_item = [items objectAtIndex: i];
1442
1443         /* Refuse to move items that are not in the General Node
1444            (Service Discovery) */
1445         if( (![self isItem: [o_item pointerValue] inNode: p_playlist->p_local_category checkItemExistence: NO] &&
1446             var_CreateGetBool( p_playlist, "media-library" ) && ![self isItem: [o_item pointerValue] inNode: p_playlist->p_ml_category checkItemExistence: NO]) ||
1447             [o_item pointerValue] == p_playlist->p_local_category ||
1448             [o_item pointerValue] == p_playlist->p_ml_category )
1449         {
1450             return NO;
1451         }
1452         /* Fill the items and nodes to move in 2 different arrays */
1453         if( ((playlist_item_t *)[o_item pointerValue])->i_children > 0 )
1454             [o_nodes_array addObject: o_item];
1455         else
1456             [o_items_array addObject: o_item];
1457     }
1458
1459     /* Now we need to check if there are selected items that are in already
1460        selected nodes. In that case, we only want to move the nodes */
1461     [self removeItemsFrom: o_nodes_array ifChildrenOf: o_nodes_array];
1462     [self removeItemsFrom: o_items_array ifChildrenOf: o_nodes_array];
1463
1464     /* We add the "VLCPlaylistItemPboardType" type to be able to recognize
1465        a Drop operation coming from the playlist. */
1466
1467     [pboard declareTypes: [NSArray arrayWithObjects:
1468         @"VLCPlaylistItemPboardType", nil] owner: self];
1469     [pboard setData:[NSData data] forType:@"VLCPlaylistItemPboardType"];
1470
1471     return YES;
1472 }
1473
1474 - (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id <NSDraggingInfo>)info proposedItem:(id)item proposedChildIndex:(NSInteger)index
1475 {
1476     playlist_t *p_playlist = pl_Get( VLCIntf );
1477     NSPasteboard *o_pasteboard = [info draggingPasteboard];
1478
1479     if( !p_playlist ) return NSDragOperationNone;
1480
1481     /* Dropping ON items is not allowed if item is not a node */
1482     if( item )
1483     {
1484         if( index == NSOutlineViewDropOnItemIndex &&
1485                 ((playlist_item_t *)[item pointerValue])->i_children == -1 )
1486         {
1487             return NSDragOperationNone;
1488         }
1489     }
1490
1491     /* Don't allow on drop on playlist root element's child */
1492     if( !item && index != NSOutlineViewDropOnItemIndex)
1493     {
1494         return NSDragOperationNone;
1495     }
1496
1497     /* We refuse to drop an item in anything else than a child of the General
1498        Node. We still accept items that would be root nodes of the outlineview
1499        however, to allow drop in an empty playlist. */
1500     if( !( ([self isItem: [item pointerValue] inNode: p_playlist->p_local_category checkItemExistence: NO] ||
1501         ( var_CreateGetBool( p_playlist, "media-library" ) && [self isItem: [item pointerValue] inNode: p_playlist->p_ml_category checkItemExistence: NO] ) ) || item == nil ) )
1502     {
1503         return NSDragOperationNone;
1504     }
1505
1506     /* Drop from the Playlist */
1507     if( [[o_pasteboard types] containsObject: @"VLCPlaylistItemPboardType"] )
1508     {
1509         NSUInteger count = [o_nodes_array count];
1510         for( NSUInteger i = 0 ; i < count ; i++ )
1511         {
1512             /* We refuse to Drop in a child of an item we are moving */
1513             if( [self isItem: [item pointerValue] inNode:
1514                     [[o_nodes_array objectAtIndex: i] pointerValue]
1515                     checkItemExistence: NO] )
1516             {
1517                 return NSDragOperationNone;
1518             }
1519         }
1520         return NSDragOperationMove;
1521     }
1522
1523     /* Drop from the Finder */
1524     else if( [[o_pasteboard types] containsObject: NSFilenamesPboardType] )
1525     {
1526         return NSDragOperationGeneric;
1527     }
1528     return NSDragOperationNone;
1529 }
1530
1531 - (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id <NSDraggingInfo>)info item:(id)item childIndex:(NSInteger)index
1532 {
1533     playlist_t * p_playlist =  pl_Get( VLCIntf );
1534     NSPasteboard *o_pasteboard = [info draggingPasteboard];
1535
1536     /* Drag & Drop inside the playlist */
1537     if( [[o_pasteboard types] containsObject: @"VLCPlaylistItemPboardType"] )
1538     {
1539         int i_row, i_removed_from_node = 0;
1540         playlist_item_t *p_new_parent, *p_item = NULL;
1541         NSArray *o_all_items = [o_nodes_array arrayByAddingObjectsFromArray:
1542                                                                 o_items_array];
1543         /* If the item is to be dropped as root item of the outline, make it a
1544            child of the General node.
1545            Else, choose the proposed parent as parent. */
1546         if( item == nil ) p_new_parent = p_playlist->p_local_category;
1547         else p_new_parent = [item pointerValue];
1548
1549         /* Make sure the proposed parent is a node.
1550            (This should never be true) */
1551         if( p_new_parent->i_children < 0 )
1552         {
1553             return NO;
1554         }
1555
1556         NSUInteger count = [o_all_items count];
1557         for( NSUInteger i = 0; i < count; i++ )
1558         {
1559             playlist_item_t *p_old_parent = NULL;
1560             int i_old_index = 0;
1561
1562             p_item = [[o_all_items objectAtIndex:i] pointerValue];
1563             p_old_parent = p_item->p_parent;
1564             if( !p_old_parent )
1565             continue;
1566             /* We may need the old index later */
1567             if( p_new_parent == p_old_parent )
1568             {
1569                 for( NSInteger j = 0; j < p_old_parent->i_children; j++ )
1570                 {
1571                     if( p_old_parent->pp_children[j] == p_item )
1572                     {
1573                         i_old_index = j;
1574                         break;
1575                     }
1576                 }
1577             }
1578
1579             PL_LOCK;
1580             // Actually detach the item from the old position
1581             if( playlist_NodeRemoveItem( p_playlist, p_item, p_old_parent ) ==
1582                 VLC_SUCCESS )
1583             {
1584                 int i_new_index;
1585                 /* Calculate the new index */
1586                 if( index == -1 )
1587                 i_new_index = -1;
1588                 /* If we move the item in the same node, we need to take into
1589                    account that one item will be deleted */
1590                 else
1591                 {
1592                     if ((p_new_parent == p_old_parent && i_old_index < index + (int)i) )
1593                     {
1594                         i_removed_from_node++;
1595                     }
1596                     i_new_index = index + i - i_removed_from_node;
1597                 }
1598                 // Reattach the item to the new position
1599                 playlist_NodeInsert( p_playlist, p_item, p_new_parent, i_new_index );
1600             }
1601             PL_UNLOCK;
1602         }
1603         [self playlistUpdated];
1604         i_row = [o_outline_view rowForItem:[o_outline_dict objectForKey:[NSString stringWithFormat: @"%p", [[o_all_items objectAtIndex: 0] pointerValue]]]];
1605
1606         if( i_row == -1 )
1607         {
1608             i_row = [o_outline_view rowForItem:[o_outline_dict objectForKey:[NSString stringWithFormat: @"%p", p_new_parent]]];
1609         }
1610
1611         [o_outline_view deselectAll: self];
1612         [o_outline_view selectRowIndexes:[NSIndexSet indexSetWithIndex:i_row] byExtendingSelection:NO];
1613         [o_outline_view scrollRowToVisible: i_row];
1614
1615         return YES;
1616     }
1617
1618     else if( [[o_pasteboard types] containsObject: NSFilenamesPboardType] )
1619     {
1620         playlist_item_t *p_node = [item pointerValue];
1621
1622         NSArray *o_values = [[o_pasteboard propertyListForType: NSFilenamesPboardType]
1623                                 sortedArrayUsingSelector: @selector(caseInsensitiveCompare:)];
1624         NSUInteger count = [o_values count];
1625         NSMutableArray *o_array = [NSMutableArray arrayWithCapacity:count];
1626
1627         for( NSUInteger i = 0; i < count; i++)
1628         {
1629             NSDictionary *o_dic;
1630             char *psz_uri = make_URI([[o_values objectAtIndex:i] UTF8String], NULL);
1631             if( !psz_uri )
1632                 continue;
1633
1634             o_dic = [NSDictionary dictionaryWithObject:[NSString stringWithCString:psz_uri encoding:NSUTF8StringEncoding] forKey:@"ITEM_URL"];
1635
1636             free( psz_uri );
1637
1638             [o_array addObject: o_dic];
1639         }
1640
1641         if ( item == nil )
1642         {
1643             [self appendArray:o_array atPos:index enqueue: YES];
1644         }
1645         else
1646         {
1647             assert( p_node->i_children != -1 );
1648             [self appendNodeArray:o_array inNode: p_node atPos:index enqueue:YES];
1649         }
1650         return YES;
1651     }
1652     return NO;
1653 }
1654 @end
1655
1656