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