]> git.sesse.net Git - vlc/blob - modules/gui/macosx/playlist.m
macosx: restore playlist column selection, position and siz (close #501)
[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 )
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     playlist_item_t * p_item = [[o_outline_view itemAtRow:[o_outline_view selectedRow]] pointerValue];
932     NSMutableString * o_mrl = nil;
933
934     if(! p_item || !p_item->p_input )
935         return;
936
937     char *psz_uri = decode_URI( input_item_GetURI( p_item->p_input ) );
938     if( psz_uri )
939         o_mrl = [NSMutableString stringWithUTF8String: psz_uri];
940
941     /* perform some checks whether it is a file and if it is local at all... */
942     NSRange prefix_range = [o_mrl rangeOfString: @"file:"];
943     if( prefix_range.location != NSNotFound )
944         [o_mrl deleteCharactersInRange: prefix_range];
945
946     if( [o_mrl characterAtIndex:0] == '/' )
947         [[NSWorkspace sharedWorkspace] selectFile: o_mrl inFileViewerRootedAtPath: o_mrl];
948 }
949
950 /* When called retrieves the selected outlineview row and plays that node or item */
951 - (IBAction)preparseItem:(id)sender
952 {
953     int i_count;
954     NSIndexSet *o_selected_indexes;
955     intf_thread_t * p_intf = VLCIntf;
956     playlist_t * p_playlist = pl_Get( p_intf );
957     playlist_item_t *p_item = NULL;
958
959     o_selected_indexes = [o_outline_view selectedRowIndexes];
960     i_count = [o_selected_indexes count];
961
962     NSUInteger indexes[i_count];
963     [o_selected_indexes getIndexes:indexes maxCount:i_count inIndexRange:nil];
964     for (int i = 0; i < i_count; i++)
965     {
966         p_item = [[o_outline_view itemAtRow:indexes[i]] pointerValue];
967         [o_outline_view deselectRow: indexes[i]];
968
969         if( p_item )
970         {
971             if( p_item->i_children == -1 )
972                 playlist_PreparseEnqueue( p_playlist, p_item->p_input );
973             else
974                 msg_Dbg( p_intf, "preparsing nodes not implemented" );
975         }
976     }
977     [self playlistUpdated];
978 }
979
980 - (IBAction)downloadCoverArt:(id)sender
981 {
982     int i_count;
983     NSIndexSet *o_selected_indexes;
984     intf_thread_t * p_intf = VLCIntf;
985     playlist_t * p_playlist = pl_Get( p_intf );
986     playlist_item_t *p_item = NULL;
987
988     o_selected_indexes = [o_outline_view selectedRowIndexes];
989     i_count = [o_selected_indexes count];
990
991     NSUInteger indexes[i_count];
992     [o_selected_indexes getIndexes:indexes maxCount:i_count inIndexRange:nil];
993     for (int i = 0; i < i_count; i++)
994     {
995         p_item = [[o_outline_view itemAtRow: indexes[i]] pointerValue];   
996         [o_outline_view deselectRow: indexes[i]];
997
998         if( p_item && p_item->i_children == -1 )
999             playlist_AskForArtEnqueue( p_playlist, p_item->p_input );
1000     }
1001     [self playlistUpdated];
1002 }
1003
1004 - (IBAction)selectAll:(id)sender
1005 {
1006     [o_outline_view selectAll: nil];
1007 }
1008
1009 - (IBAction)deleteItem:(id)sender
1010 {
1011     int i_count;
1012     NSIndexSet *o_selected_indexes;
1013     playlist_t * p_playlist;
1014     intf_thread_t * p_intf = VLCIntf;
1015
1016     o_selected_indexes = [o_outline_view selectedRowIndexes];
1017     i_count = [o_selected_indexes count];
1018
1019     p_playlist = pl_Get( p_intf );
1020
1021     NSUInteger indexes[i_count];
1022     if (i_count == [o_outline_view numberOfRows])
1023     {
1024 #ifndef NDEBUG
1025         msg_Dbg( p_intf, "user selected entire list, deleting current playlist root instead of individual items" );
1026 #endif
1027         PL_LOCK;
1028         playlist_NodeDelete( p_playlist, [self currentPlaylistRoot], true, false );
1029         PL_UNLOCK;
1030         [self playlistUpdated];
1031         return;
1032     }
1033     [o_selected_indexes getIndexes:indexes maxCount:i_count inIndexRange:nil];
1034     for (int i = 0; i < i_count; i++)
1035     {
1036         id o_item = [o_outline_view itemAtRow: indexes[i]];
1037         [o_outline_view deselectRow: indexes[i]];
1038
1039         PL_LOCK;
1040         playlist_item_t *p_item = [o_item pointerValue];
1041 #ifndef NDEBUG
1042         msg_Dbg( p_intf, "deleting item %i (of %i) with id \"%i\", pointerValue \"%p\" and %i children", i+1, i_count,
1043                 p_item->p_input->i_id, [o_item pointerValue], p_item->i_children +1 );
1044 #endif
1045
1046         if( p_item->i_children != -1 )
1047         //is a node and not an item
1048         {
1049             if( playlist_Status( p_playlist ) != PLAYLIST_STOPPED &&
1050                 [self isItem: playlist_CurrentPlayingItem( p_playlist ) inNode: ((playlist_item_t *)[o_item pointerValue])
1051                         checkItemExistence: NO locked:YES] == YES )
1052                 // if current item is in selected node and is playing then stop playlist
1053                 playlist_Control(p_playlist, PLAYLIST_STOP, pl_Locked );
1054
1055                 playlist_NodeDelete( p_playlist, p_item, true, false );
1056         }
1057         else
1058             playlist_DeleteFromInput( p_playlist, p_item->p_input, pl_Locked );
1059
1060         PL_UNLOCK;
1061         [o_outline_dict removeObjectForKey:[NSString stringWithFormat:@"%p", [o_item pointerValue]]];
1062         [o_item release];
1063     }
1064
1065     [self playlistUpdated];
1066 }
1067
1068 - (IBAction)sortNodeByName:(id)sender
1069 {
1070     [self sortNode: SORT_TITLE];
1071 }
1072
1073 - (IBAction)sortNodeByAuthor:(id)sender
1074 {
1075     [self sortNode: SORT_ARTIST];
1076 }
1077
1078 - (void)sortNode:(int)i_mode
1079 {
1080     playlist_t * p_playlist = pl_Get( VLCIntf );
1081     playlist_item_t * p_item;
1082
1083     if( [o_outline_view selectedRow] > -1 )
1084     {
1085         p_item = [[o_outline_view itemAtRow: [o_outline_view selectedRow]] pointerValue];
1086     }
1087     else
1088     /*If no item is selected, sort the whole playlist*/
1089     {
1090         p_item = [self currentPlaylistRoot];
1091     }
1092
1093     PL_LOCK;
1094     if( p_item->i_children > -1 ) // the item is a node
1095     {
1096         playlist_RecursiveNodeSort( p_playlist, p_item, i_mode, ORDER_NORMAL );
1097     }
1098     else
1099     {
1100         playlist_RecursiveNodeSort( p_playlist,
1101                 p_item->p_parent, i_mode, ORDER_NORMAL );
1102     }
1103     PL_UNLOCK;
1104     [self playlistUpdated];
1105 }
1106
1107 - (input_item_t *)createItem:(NSDictionary *)o_one_item
1108 {
1109     intf_thread_t * p_intf = VLCIntf;
1110     playlist_t * p_playlist = pl_Get( p_intf );
1111
1112     input_item_t *p_input;
1113     BOOL b_rem = FALSE, b_dir = FALSE, b_writable = FALSE;
1114     NSString *o_uri, *o_name, *o_path;
1115     NSURL * o_nsurl;
1116     NSArray *o_options;
1117     NSURL *o_true_file;
1118
1119     /* Get the item */
1120     o_uri = (NSString *)[o_one_item objectForKey: @"ITEM_URL"];
1121     o_nsurl = [NSURL URLWithString: o_uri];
1122     o_path = [o_nsurl path];
1123     o_name = (NSString *)[o_one_item objectForKey: @"ITEM_NAME"];
1124     o_options = (NSArray *)[o_one_item objectForKey: @"ITEM_OPTIONS"];
1125
1126     if( [[NSFileManager defaultManager] fileExistsAtPath:o_path isDirectory:&b_dir] && b_dir &&
1127         [[NSWorkspace sharedWorkspace] getFileSystemInfoForPath:o_path isRemovable: &b_rem
1128                                                      isWritable:&b_writable isUnmountable:NULL description:NULL type:NULL] && b_rem && !b_writable && [o_nsurl isFileURL] )
1129     {
1130
1131         id o_vlc_open = [[VLCMain sharedInstance] open];
1132
1133         char *diskType = [o_vlc_open getVolumeTypeFromMountPath: o_path];
1134         msg_Dbg( p_intf, "detected optical media of type '%s' in the file input", diskType );
1135
1136         if (diskType == kVLCMediaDVD)
1137         {
1138             o_uri = [NSString stringWithFormat: @"dvdnav://%@", [o_vlc_open getBSDNodeFromMountPath: o_path]];
1139         }
1140         else if (diskType == kVLCMediaVideoTSFolder)
1141         {
1142             o_uri = [NSString stringWithFormat: @"dvdnav://%@", o_path];
1143         }
1144         else if (diskType == kVLCMediaAudioCD)
1145         {
1146             o_uri = [NSString stringWithFormat: @"cdda://%@", [o_vlc_open getBSDNodeFromMountPath: o_path]];
1147         }
1148         else if (diskType == kVLCMediaVCD)
1149         {
1150             o_uri = [NSString stringWithFormat: @"vcd://%@#0:0", [o_vlc_open getBSDNodeFromMountPath: o_path]];
1151         }
1152         else if (diskType == kVLCMediaSVCD)
1153         {
1154             o_uri = [NSString stringWithFormat: @"vcd://%@@0:0", [o_vlc_open getBSDNodeFromMountPath: o_path]];
1155         }
1156         else if (diskType == kVLCMediaBD || diskType == kVLCMediaBDMVFolder)
1157         {
1158             o_uri = [NSString stringWithFormat: @"bluray://%@", o_path];
1159         }
1160         else
1161         {
1162             msg_Warn( VLCIntf, "unknown disk type, treating %s as regular input", [o_path UTF8String] );
1163         }
1164
1165         p_input = input_item_New( [o_uri UTF8String], [[[NSFileManager defaultManager] displayNameAtPath: o_path] UTF8String] );
1166     }
1167     else
1168         p_input = input_item_New( [o_uri fileSystemRepresentation], o_name ? [o_name UTF8String] : NULL );
1169
1170     if( !p_input )
1171         return NULL;
1172
1173     if( o_options )
1174     {
1175         NSUInteger count = [o_options count];
1176         for( NSUInteger i = 0; i < count; i++ )
1177         {
1178             input_item_AddOption( p_input, [[o_options objectAtIndex:i] UTF8String],
1179                                   VLC_INPUT_OPTION_TRUSTED );
1180         }
1181     }
1182
1183     /* Recent documents menu */
1184     if( o_nsurl != nil && (BOOL)config_GetInt( p_playlist, "macosx-recentitems" ) == YES )
1185     {
1186         [[NSDocumentController sharedDocumentController] noteNewRecentDocumentURL: o_nsurl];
1187     }
1188     return p_input;
1189 }
1190
1191 - (void)appendArray:(NSArray*)o_array atPos:(int)i_position enqueue:(BOOL)b_enqueue
1192 {
1193     playlist_t * p_playlist = pl_Get( VLCIntf );
1194     NSUInteger count = [o_array count];
1195     BOOL b_usingPlaylist;
1196     if ([self currentPlaylistRoot] == p_playlist->p_ml_category)
1197         b_usingPlaylist = NO;
1198     else
1199         b_usingPlaylist = YES;
1200
1201     PL_LOCK;
1202     for( NSUInteger i_item = 0; i_item < count; i_item++ )
1203     {
1204         input_item_t *p_input;
1205         NSDictionary *o_one_item;
1206
1207         /* Get the item */
1208         o_one_item = [o_array objectAtIndex: i_item];
1209         p_input = [self createItem: o_one_item];
1210         if( !p_input )
1211         {
1212             continue;
1213         }
1214
1215         /* Add the item */
1216         /* FIXME: playlist_AddInput() can fail */
1217
1218         playlist_AddInput( p_playlist, p_input, PLAYLIST_INSERT, i_position == -1 ? PLAYLIST_END : i_position + i_item, b_usingPlaylist,
1219          pl_Locked );
1220
1221         if( i_item == 0 && !b_enqueue )
1222         {
1223             playlist_item_t *p_item = playlist_ItemGetByInput( p_playlist, p_input );
1224             playlist_Control( p_playlist, PLAYLIST_VIEWPLAY, pl_Locked, p_item->p_parent, p_item );
1225         }
1226
1227         vlc_gc_decref( p_input );
1228     }
1229     PL_UNLOCK;
1230     [self playlistUpdated];
1231 }
1232
1233 - (void)appendNodeArray:(NSArray*)o_array inNode:(playlist_item_t *)p_node atPos:(int)i_position enqueue:(BOOL)b_enqueue
1234 {
1235     playlist_t * p_playlist = pl_Get( VLCIntf );
1236     NSUInteger count = [o_array count];
1237
1238     for( NSUInteger i_item = 0; i_item < count; i_item++ )
1239     {
1240         input_item_t *p_input;
1241         NSDictionary *o_one_item;
1242
1243         /* Get the item */
1244         o_one_item = [o_array objectAtIndex: i_item];
1245         p_input = [self createItem: o_one_item];
1246
1247         if( !p_input ) continue;
1248
1249         /* Add the item */
1250         PL_LOCK;
1251         playlist_NodeAddInput( p_playlist, p_input, p_node,
1252                                       PLAYLIST_INSERT,
1253                                       i_position == -1 ?
1254                                       PLAYLIST_END : i_position + i_item,
1255                                       pl_Locked );
1256
1257
1258         if( i_item == 0 && !b_enqueue )
1259         {
1260             playlist_item_t *p_item;
1261             p_item = playlist_ItemGetByInput( p_playlist, p_input );
1262             playlist_Control( p_playlist, PLAYLIST_VIEWPLAY, pl_Locked, p_node, p_item );
1263         }
1264         PL_UNLOCK;
1265         vlc_gc_decref( p_input );
1266     }
1267     [self playlistUpdated];
1268 }
1269
1270 - (NSMutableArray *)subSearchItem:(playlist_item_t *)p_item
1271 {
1272     playlist_t *p_playlist = pl_Get( VLCIntf );
1273     playlist_item_t *p_selected_item;
1274     int i_selected_row;
1275
1276     i_selected_row = [o_outline_view selectedRow];
1277     if (i_selected_row < 0)
1278         i_selected_row = 0;
1279
1280     p_selected_item = (playlist_item_t *)[[o_outline_view itemAtRow: i_selected_row] pointerValue];
1281
1282     for( NSUInteger i_current = 0; i_current < p_item->i_children ; i_current++ )
1283     {
1284         char *psz_temp;
1285         NSString *o_current_name, *o_current_author;
1286
1287         PL_LOCK;
1288         o_current_name = [NSString stringWithUTF8String:
1289             p_item->pp_children[i_current]->p_input->psz_name];
1290         psz_temp = input_item_GetInfo( p_item->p_input ,
1291                    _("Meta-information"),_("Artist") );
1292         o_current_author = [NSString stringWithUTF8String: psz_temp];
1293         free( psz_temp);
1294         PL_UNLOCK;
1295
1296         if( p_selected_item == p_item->pp_children[i_current] &&
1297                     b_selected_item_met == NO )
1298         {
1299             b_selected_item_met = YES;
1300         }
1301         else if( p_selected_item == p_item->pp_children[i_current] &&
1302                     b_selected_item_met == YES )
1303         {
1304             return NULL;
1305         }
1306         else if( b_selected_item_met == YES &&
1307                     ( [o_current_name rangeOfString:[o_search_field
1308                         stringValue] options:NSCaseInsensitiveSearch].length ||
1309                       [o_current_author rangeOfString:[o_search_field
1310                         stringValue] options:NSCaseInsensitiveSearch].length ) )
1311         {
1312             /*Adds the parent items in the result array as well, so that we can
1313             expand the tree*/
1314             return [NSMutableArray arrayWithObject: [NSValue
1315                             valueWithPointer: p_item->pp_children[i_current]]];
1316         }
1317         if( p_item->pp_children[i_current]->i_children > 0 )
1318         {
1319             id o_result = [self subSearchItem:
1320                                             p_item->pp_children[i_current]];
1321             if( o_result != NULL )
1322             {
1323                 [o_result insertObject: [NSValue valueWithPointer:
1324                                 p_item->pp_children[i_current]] atIndex:0];
1325                 return o_result;
1326             }
1327         }
1328     }
1329     return NULL;
1330 }
1331
1332 - (IBAction)searchItem:(id)sender
1333 {
1334     playlist_t * p_playlist = pl_Get( VLCIntf );
1335     id o_result;
1336
1337     int i_row = -1;
1338
1339     b_selected_item_met = NO;
1340
1341         /*First, only search after the selected item:*
1342          *(b_selected_item_met = NO)                 */
1343     o_result = [self subSearchItem:[self currentPlaylistRoot]];
1344     if( o_result == NULL )
1345     {
1346         /* If the first search failed, search again from the beginning */
1347         o_result = [self subSearchItem:[self currentPlaylistRoot]];
1348     }
1349     if( o_result != NULL )
1350     {
1351         int i_start;
1352         if( [[o_result objectAtIndex: 0] pointerValue] == p_playlist->p_local_category )
1353             i_start = 1;
1354         else
1355             i_start = 0;
1356         NSUInteger count = [o_result count];
1357
1358         for( NSUInteger i = i_start ; i < count - 1 ; i++ )
1359         {
1360             [o_outline_view expandItem: [o_outline_dict objectForKey:
1361                         [NSString stringWithFormat: @"%p",
1362                         [[o_result objectAtIndex: i] pointerValue]]]];
1363         }
1364         i_row = [o_outline_view rowForItem: [o_outline_dict objectForKey:
1365                         [NSString stringWithFormat: @"%p",
1366                         [[o_result objectAtIndex: count - 1 ]
1367                         pointerValue]]]];
1368     }
1369     if( i_row > -1 )
1370     {
1371         [o_outline_view selectRowIndexes:[NSIndexSet indexSetWithIndex:i_row] byExtendingSelection:NO];
1372         [o_outline_view scrollRowToVisible: i_row];
1373     }
1374 }
1375
1376 - (IBAction)recursiveExpandNode:(id)sender
1377 {
1378     id o_item = [o_outline_view itemAtRow: [o_outline_view selectedRow]];
1379     playlist_item_t *p_item = (playlist_item_t *)[o_item pointerValue];
1380
1381     if( ![[o_outline_view dataSource] outlineView: o_outline_view
1382                                                     isItemExpandable: o_item] )
1383     {
1384         o_item = [o_outline_dict objectForKey: [NSString stringWithFormat: @"%p", p_item->p_parent]];
1385     }
1386
1387     /* We need to collapse the node first, since OSX refuses to recursively
1388        expand an already expanded node, even if children nodes are collapsed. */
1389     [o_outline_view collapseItem: o_item collapseChildren: YES];
1390     [o_outline_view expandItem: o_item expandChildren: YES];
1391 }
1392
1393 - (NSMenu *)menuForEvent:(NSEvent *)o_event
1394 {
1395     NSPoint pt;
1396     bool b_rows;
1397     bool b_item_sel;
1398
1399     pt = [o_outline_view convertPoint: [o_event locationInWindow]
1400                                                  fromView: nil];
1401     int row = [o_outline_view rowAtPoint:pt];
1402     if( row != -1 )
1403         [o_outline_view selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO];
1404
1405     b_item_sel = ( row != -1 && [o_outline_view selectedRow] != -1 );
1406     b_rows = [o_outline_view numberOfRows] != 0;
1407
1408     [o_mi_play setEnabled: b_item_sel];
1409     [o_mi_delete setEnabled: b_item_sel];
1410     [o_mi_selectall setEnabled: b_rows];
1411     [o_mi_info setEnabled: b_item_sel];
1412     [o_mi_preparse setEnabled: b_item_sel];
1413     [o_mi_recursive_expand setEnabled: b_item_sel];
1414     [o_mi_sort_name setEnabled: b_item_sel];
1415     [o_mi_sort_author setEnabled: b_item_sel];
1416
1417     return( o_ctx_menu );
1418 }
1419
1420 - (void)outlineView: (NSOutlineView *)o_tv
1421                   didClickTableColumn:(NSTableColumn *)o_tc
1422 {
1423     int i_mode, i_type = 0;
1424     intf_thread_t *p_intf = VLCIntf;
1425     NSString * o_identifier = [o_tc identifier];
1426
1427     playlist_t *p_playlist = pl_Get( p_intf );
1428
1429     /* Check whether the selected table column header corresponds to a
1430        sortable table column*/
1431     if( !( [o_identifier isEqualToString:TITLE_COLUMN] || [o_identifier isEqualToString:ARTIST_COLUMN] || [o_identifier isEqualToString:DURATION_COLUMN] ) )
1432         return;
1433
1434     if( o_tc_sortColumn == o_tc )
1435         b_isSortDescending = !b_isSortDescending;
1436     else
1437         b_isSortDescending = false;
1438
1439     if( [o_identifier isEqualToString:TITLE_COLUMN] )
1440         i_mode = SORT_TITLE;
1441     else if( [o_identifier isEqualToString:ARTIST_COLUMN] )
1442         i_mode = SORT_ARTIST;
1443     else if( [o_identifier isEqualToString:DURATION_COLUMN] )
1444         i_mode = SORT_DURATION;
1445
1446     if( b_isSortDescending )
1447         i_type = ORDER_REVERSE;
1448     else
1449         i_type = ORDER_NORMAL;
1450
1451     PL_LOCK;
1452     playlist_RecursiveNodeSort( p_playlist, [self currentPlaylistRoot], i_mode, i_type );
1453     PL_UNLOCK;
1454
1455     [self playlistUpdated];
1456
1457     o_tc_sortColumn = o_tc;
1458     [o_outline_view setHighlightedTableColumn:o_tc];
1459
1460     if( b_isSortDescending )
1461         [o_outline_view setIndicatorImage:o_descendingSortingImage inTableColumn:o_tc];
1462     else
1463         [o_outline_view setIndicatorImage:o_ascendingSortingImage inTableColumn:o_tc];
1464 }
1465
1466
1467 - (void)outlineView:(NSOutlineView *)outlineView
1468                                 willDisplayCell:(id)cell
1469                                 forTableColumn:(NSTableColumn *)tableColumn
1470                                 item:(id)item
1471 {
1472     /* this method can be called when VLC is already dead, hence the extra checks */
1473     intf_thread_t * p_intf = VLCIntf;
1474     if (!p_intf)
1475         return;
1476     playlist_t *p_playlist = pl_Get( p_intf );
1477     if (!p_playlist)
1478         return;
1479
1480     id o_playing_item;
1481
1482     PL_LOCK;
1483     o_playing_item = [o_outline_dict objectForKey: [NSString stringWithFormat:@"%p",  playlist_CurrentPlayingItem( p_playlist )]];
1484     PL_UNLOCK;
1485
1486     if( [self isItem: [o_playing_item pointerValue] inNode:
1487                         [item pointerValue] checkItemExistence: YES]
1488                         || [o_playing_item isEqual: item] )
1489     {
1490         [cell setFont: [[NSFontManager sharedFontManager] convertFont:[cell font] toHaveTrait:NSBoldFontMask]];
1491     }
1492     else
1493     {
1494         [cell setFont: [[NSFontManager sharedFontManager] convertFont:[cell font] toNotHaveTrait:NSBoldFontMask]];
1495     }
1496 }
1497
1498 - (id)playingItem
1499 {
1500     playlist_t *p_playlist = pl_Get( VLCIntf );
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     return o_playing_item;
1509 }
1510
1511 - (NSArray *)draggedItems
1512 {
1513     return [[o_nodes_array arrayByAddingObjectsFromArray: o_items_array] retain];
1514 }
1515 @end
1516
1517 @implementation VLCPlaylist (NSOutlineViewDataSource)
1518
1519 - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item
1520 {
1521     id o_value = [super outlineView: outlineView child: index ofItem: item];
1522
1523     [o_outline_dict setObject:o_value forKey:[NSString stringWithFormat:@"%p", [o_value pointerValue]]];
1524     return o_value;
1525 }
1526
1527 /* Required for drag & drop and reordering */
1528 - (BOOL)outlineView:(NSOutlineView *)outlineView writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pboard
1529 {
1530     playlist_t *p_playlist = pl_Get( VLCIntf );
1531
1532     /* First remove the items that were moved during the last drag & drop
1533        operation */
1534     [o_items_array removeAllObjects];
1535     [o_nodes_array removeAllObjects];
1536
1537     NSUInteger itemCount = [items count];
1538
1539     for( NSUInteger i = 0 ; i < itemCount ; i++ )
1540     {
1541         id o_item = [items objectAtIndex: i];
1542
1543         /* Fill the items and nodes to move in 2 different arrays */
1544         if( ((playlist_item_t *)[o_item pointerValue])->i_children > 0 )
1545             [o_nodes_array addObject: o_item];
1546         else
1547             [o_items_array addObject: o_item];
1548     }
1549
1550     /* Now we need to check if there are selected items that are in already
1551        selected nodes. In that case, we only want to move the nodes */
1552     [self removeItemsFrom: o_nodes_array ifChildrenOf: o_nodes_array];
1553     [self removeItemsFrom: o_items_array ifChildrenOf: o_nodes_array];
1554
1555     /* We add the "VLCPlaylistItemPboardType" type to be able to recognize
1556        a Drop operation coming from the playlist. */
1557
1558     [pboard declareTypes: [NSArray arrayWithObjects:
1559         @"VLCPlaylistItemPboardType", nil] owner: self];
1560     [pboard setData:[NSData data] forType:@"VLCPlaylistItemPboardType"];
1561
1562     return YES;
1563 }
1564
1565 - (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id <NSDraggingInfo>)info proposedItem:(id)item proposedChildIndex:(NSInteger)index
1566 {
1567     playlist_t *p_playlist = pl_Get( VLCIntf );
1568     NSPasteboard *o_pasteboard = [info draggingPasteboard];
1569
1570     if( !p_playlist ) return NSDragOperationNone;
1571
1572     /* Dropping ON items is not allowed if item is not a node */
1573     if( item )
1574     {
1575         if( index == NSOutlineViewDropOnItemIndex &&
1576                 ((playlist_item_t *)[item pointerValue])->i_children == -1 )
1577         {
1578             return NSDragOperationNone;
1579         }
1580     }
1581
1582     /* We refuse to drop an item in anything else than a child of the General
1583        Node. We still accept items that would be root nodes of the outlineview
1584        however, to allow drop in an empty playlist. */
1585     if( !( ([self isItem: [item pointerValue] inNode: p_playlist->p_local_category checkItemExistence: NO] ||
1586         ( var_CreateGetBool( p_playlist, "media-library" ) && [self isItem: [item pointerValue] inNode: p_playlist->p_ml_category checkItemExistence: NO] ) ) || item == nil ) )
1587     {
1588         return NSDragOperationNone;
1589     }
1590
1591     /* Drop from the Playlist */
1592     if( [[o_pasteboard types] containsObject: @"VLCPlaylistItemPboardType"] )
1593     {
1594         NSUInteger count = [o_nodes_array count];
1595         for( NSUInteger i = 0 ; i < count ; i++ )
1596         {
1597             /* We refuse to Drop in a child of an item we are moving */
1598             if( [self isItem: [item pointerValue] inNode:
1599                     [[o_nodes_array objectAtIndex: i] pointerValue]
1600                     checkItemExistence: NO] )
1601             {
1602                 return NSDragOperationNone;
1603             }
1604         }
1605         return NSDragOperationMove;
1606     }
1607
1608     /* Drop from the Finder */
1609     else if( [[o_pasteboard types] containsObject: NSFilenamesPboardType] )
1610     {
1611         return NSDragOperationGeneric;
1612     }
1613     return NSDragOperationNone;
1614 }
1615
1616 - (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id <NSDraggingInfo>)info item:(id)item childIndex:(NSInteger)index
1617 {
1618     playlist_t * p_playlist =  pl_Get( VLCIntf );
1619     NSPasteboard *o_pasteboard = [info draggingPasteboard];
1620
1621     /* Drag & Drop inside the playlist */
1622     if( [[o_pasteboard types] containsObject: @"VLCPlaylistItemPboardType"] )
1623     {
1624         int i_row, i_removed_from_node = 0;
1625         playlist_item_t *p_new_parent, *p_item = NULL;
1626         NSArray *o_all_items = [o_nodes_array arrayByAddingObjectsFromArray: o_items_array];
1627         /* If the item is to be dropped as root item of the outline, make it a
1628            child of the respective general node, if is either the pl or the ml
1629            Else, choose the proposed parent as parent. */
1630         if( item == nil )
1631         {
1632             if ([self currentPlaylistRoot] == p_playlist->p_local_category || [self currentPlaylistRoot] == p_playlist->p_ml_category) 
1633                 p_new_parent = [self currentPlaylistRoot];
1634             else
1635                 return NO;
1636         }
1637         else
1638             p_new_parent = [item pointerValue];
1639
1640         /* Make sure the proposed parent is a node.
1641            (This should never be true) */
1642         if( p_new_parent->i_children < 0 )
1643         {
1644             return NO;
1645         }
1646
1647         NSUInteger count = [o_all_items count];
1648         for( NSUInteger i = 0; i < count; i++ )
1649         {
1650             playlist_item_t *p_old_parent = NULL;
1651             int i_old_index = 0;
1652
1653             p_item = [[o_all_items objectAtIndex:i] pointerValue];
1654             p_old_parent = p_item->p_parent;
1655             if( !p_old_parent )
1656             continue;
1657             /* We may need the old index later */
1658             if( p_new_parent == p_old_parent )
1659             {
1660                 for( NSInteger j = 0; j < p_old_parent->i_children; j++ )
1661                 {
1662                     if( p_old_parent->pp_children[j] == p_item )
1663                     {
1664                         i_old_index = j;
1665                         break;
1666                     }
1667                 }
1668             }
1669
1670             PL_LOCK;
1671             // Actually detach the item from the old position
1672             if( playlist_NodeRemoveItem( p_playlist, p_item, p_old_parent ) ==
1673                 VLC_SUCCESS )
1674             {
1675                 int i_new_index;
1676                 /* Calculate the new index */
1677                 if( index == -1 )
1678                 i_new_index = -1;
1679                 /* If we move the item in the same node, we need to take into
1680                    account that one item will be deleted */
1681                 else
1682                 {
1683                     if ((p_new_parent == p_old_parent && i_old_index < index + (int)i) )
1684                     {
1685                         i_removed_from_node++;
1686                     }
1687                     i_new_index = index + i - i_removed_from_node;
1688                 }
1689                 // Reattach the item to the new position
1690                 playlist_NodeInsert( p_playlist, p_item, p_new_parent, i_new_index );
1691             }
1692             PL_UNLOCK;
1693         }
1694         [self playlistUpdated];
1695         i_row = [o_outline_view rowForItem:[o_outline_dict objectForKey:[NSString stringWithFormat: @"%p", [[o_all_items objectAtIndex: 0] pointerValue]]]];
1696
1697         if( i_row == -1 )
1698         {
1699             i_row = [o_outline_view rowForItem:[o_outline_dict objectForKey:[NSString stringWithFormat: @"%p", p_new_parent]]];
1700         }
1701
1702         [o_outline_view deselectAll: self];
1703         [o_outline_view selectRowIndexes:[NSIndexSet indexSetWithIndex:i_row] byExtendingSelection:NO];
1704         [o_outline_view scrollRowToVisible: i_row];
1705
1706         return YES;
1707     }
1708
1709     else if( [[o_pasteboard types] containsObject: NSFilenamesPboardType] )
1710     {
1711         if ([self currentPlaylistRoot] != p_playlist->p_local_category && [self currentPlaylistRoot] != p_playlist->p_ml_category) 
1712             return NO;
1713
1714         playlist_item_t *p_node = [item pointerValue];
1715
1716         NSArray *o_values = [[o_pasteboard propertyListForType: NSFilenamesPboardType]
1717                                 sortedArrayUsingSelector: @selector(caseInsensitiveCompare:)];
1718         NSUInteger count = [o_values count];
1719         NSMutableArray *o_array = [NSMutableArray arrayWithCapacity:count];
1720         input_thread_t * p_input = pl_CurrentInput( VLCIntf );
1721         BOOL b_returned = NO;
1722
1723         if (count == 1 && p_input)
1724         {
1725             b_returned = input_AddSubtitle( p_input, make_URI([[o_values objectAtIndex:0] UTF8String], NULL), true );
1726             vlc_object_release( p_input );
1727             if(!b_returned)
1728                 return YES;
1729         }
1730         else if( p_input )
1731             vlc_object_release( p_input );
1732
1733         for( NSUInteger i = 0; i < count; i++)
1734         {
1735             NSDictionary *o_dic;
1736             char *psz_uri = make_URI([[o_values objectAtIndex:i] UTF8String], NULL);
1737             if( !psz_uri )
1738                 continue;
1739
1740             o_dic = [NSDictionary dictionaryWithObject:[NSString stringWithCString:psz_uri encoding:NSUTF8StringEncoding] forKey:@"ITEM_URL"];
1741
1742             free( psz_uri );
1743
1744             [o_array addObject: o_dic];
1745         }
1746
1747         if ( item == nil )
1748         {
1749             [self appendArray:o_array atPos:index enqueue: YES];
1750         }
1751         else
1752         {
1753             assert( p_node->i_children != -1 );
1754             [self appendNodeArray:o_array inNode: p_node atPos:index enqueue:YES];
1755         }
1756         return YES;
1757     }
1758     return NO;
1759 }
1760
1761 @end