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