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