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