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