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