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