macosx: Remove private API for sort indicator images
[vlc.git] / modules / gui / macosx / VLCPlaylist.m
blob3d8b98b7ed8e55084b523777fc1259f206fd6654
1 /*****************************************************************************
2  * VLCPlaylist.m: MacOS X interface module
3  *****************************************************************************
4 * Copyright (C) 2002-2015 VLC authors and VideoLAN
5  * $Id$
6  *
7  * Authors: Derk-Jan Hartman <hartman at videola/n dot org>
8  *          Benjamin Pracht <bigben at videolan dot org>
9  *          Felix Paul Kühne <fkuehne at videolan dot org>
10  *          David Fuhrmann <david dot fuhrmann at googlemail dot com>
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  *****************************************************************************/
27 /* TODO
28  * add 'icons' for different types of nodes? (http://www.cocoadev.com/index.pl?IconAndTextInTableCell)
29  * reimplement enable/disable item
30  */
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>
42 #import "CompatibilityFixes.h"
44 #import "VLCMain.h"
45 #import "VLCPlaylist.h"
46 #import "VLCMainMenu.h"
47 #import "VLCPlaylistInfo.h"
48 #import "VLCResumeDialogController.h"
49 #import "VLCOpenWindowController.h"
51 #include <vlc_actions.h>
52 #import <vlc_interface.h>
53 #include <vlc_url.h>
55 @interface VLCPlaylist ()
57     NSImage *_descendingSortingImage;
58     NSImage *_ascendingSortingImage;
60     BOOL b_selected_item_met;
61     BOOL b_isSortDescending;
62     NSTableColumn *_sortTableColumn;
64     BOOL b_playlistmenu_nib_loaded;
66     VLCPLModel *_model;
68     // information for playlist table columns menu
70     NSDictionary *_translationsForPlaylistTableColumns;
71     NSArray *_menuOrderOfPlaylistTableColumns;
74 - (void)saveTableColumns;
75 @end
77 @implementation VLCPlaylist
79 - (id)init
81     self = [super init];
82     if (self) {
83         _ascendingSortingImage = [NSImage imageNamed:@"NSAscendingSortIndicator"];
84         _descendingSortingImage = [NSImage imageNamed:@"NSDescendingSortIndicator"];
86         [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(applicationWillTerminate:) name: NSApplicationWillTerminateNotification object: nil];
89         _translationsForPlaylistTableColumns = [[NSDictionary alloc] initWithObjectsAndKeys:
90                                                 _NS("Track Number"),  TRACKNUM_COLUMN,
91                                                 _NS("Title"),         TITLE_COLUMN,
92                                                 _NS("Author"),        ARTIST_COLUMN,
93                                                 _NS("Duration"),      DURATION_COLUMN,
94                                                 _NS("Genre"),         GENRE_COLUMN,
95                                                 _NS("Album"),         ALBUM_COLUMN,
96                                                 _NS("Description"),   DESCRIPTION_COLUMN,
97                                                 _NS("Date"),          DATE_COLUMN,
98                                                 _NS("Language"),      LANGUAGE_COLUMN,
99                                                 _NS("URI"),           URI_COLUMN,
100                                                 _NS("File Size"),     FILESIZE_COLUMN,
101                                                 nil];
102         // this array also assigns tags (index) to type of menu item
103         _menuOrderOfPlaylistTableColumns = [[NSArray alloc] initWithObjects: TRACKNUM_COLUMN, TITLE_COLUMN,
104                                             ARTIST_COLUMN, DURATION_COLUMN, GENRE_COLUMN, ALBUM_COLUMN,
105                                             DESCRIPTION_COLUMN, DATE_COLUMN, LANGUAGE_COLUMN, URI_COLUMN,
106                                             FILESIZE_COLUMN,nil];
108     }
109     return self;
112 + (void)initialize
114     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
115     NSMutableArray *columnArray = [[NSMutableArray alloc] init];
116     [columnArray addObject: [NSArray arrayWithObjects:TITLE_COLUMN, [NSNumber numberWithFloat:190.], nil]];
117     [columnArray addObject: [NSArray arrayWithObjects:ARTIST_COLUMN, [NSNumber numberWithFloat:95.], nil]];
118     [columnArray addObject: [NSArray arrayWithObjects:DURATION_COLUMN, [NSNumber numberWithFloat:95.], nil]];
120     NSDictionary *appDefaults = [NSDictionary dictionaryWithObjectsAndKeys:
121                                  [NSArray arrayWithArray:columnArray], @"PlaylistColumnSelection", nil];
123     [defaults registerDefaults:appDefaults];
126 - (VLCPLModel *)model
128     return _model;
131 - (void)reloadStyles
133     NSFont *fontToUse;
134     CGFloat rowHeight;
135     if (var_InheritBool(getIntf(), "macosx-large-text")) {
136         fontToUse = [NSFont systemFontOfSize:13.];
137         rowHeight = 21.;
138     } else {
139         fontToUse = [NSFont systemFontOfSize:11.];
140         rowHeight = 16.;
141     }
143     NSArray *columns = [_outlineView tableColumns];
144     NSUInteger count = columns.count;
145     for (NSUInteger x = 0; x < count; x++)
146         [[[columns objectAtIndex:x] dataCell] setFont:fontToUse];
147     [_outlineView setRowHeight:rowHeight];
150 - (void)awakeFromNib
152     // This is only called for the playlist popup menu
153     [self initStrings];
156 - (void)setOutlineView:(VLCPlaylistView * __nullable)outlineView
158     _outlineView = outlineView;
159     [_outlineView setDelegate:self];
161     playlist_t * p_playlist = pl_Get(getIntf());
163     _model = [[VLCPLModel alloc] initWithOutlineView:_outlineView playlist:p_playlist rootItem:p_playlist->p_playing];
164     [_outlineView setDataSource:_model];
165     [_outlineView reloadData];
167     [_outlineView setTarget: self];
168     [_outlineView setDoubleAction: @selector(playItem:)];
170     [_outlineView setAllowsEmptySelection: NO];
171     [_outlineView registerForDraggedTypes: [NSArray arrayWithObjects:NSFilenamesPboardType, @"VLCPlaylistItemPboardType", nil]];
172     [_outlineView setIntercellSpacing: NSMakeSize (0.0, 1.0)];
174     [self reloadStyles];
177 - (void)setPlaylistHeaderView:(NSTableHeaderView * __nullable)playlistHeaderView
179     _playlistHeaderView = playlistHeaderView;
181     // Setup playlist table column selection for both context and main menu
182     NSMenu *contextMenu = [[NSMenu alloc] init];
183     [self setupPlaylistTableColumnsForMenu:contextMenu];
184     [_playlistHeaderView setMenu:contextMenu];
185     [self setupPlaylistTableColumnsForMenu:[[[VLCMain sharedInstance] mainMenu] playlistTableColumnsMenu]];
187     NSArray *columnArray = [[NSUserDefaults standardUserDefaults] arrayForKey:@"PlaylistColumnSelection"];
189     BOOL hasTitleItem = NO;
191     for (NSArray *column in columnArray) {
192         NSString *columnName = column[0];
193         NSNumber *columnWidth = column[1];
195         if ([columnName isEqualToString:@"status"])
196             continue;
198         // Memorize if we custom set always-enabled title item
199         if ([columnName isEqualToString:TITLE_COLUMN]) {
200             hasTitleItem = YES;
201         }
203         if(![self setPlaylistColumnTableState: NSOnState forColumn:columnName])
204             continue;
206         [[_outlineView tableColumnWithIdentifier:columnName] setWidth:[columnWidth floatValue]];
207     }
209     // Set the always enabled title item if not already done
210     if (!hasTitleItem)
211         [self setPlaylistColumnTableState:NSOnState forColumn:TITLE_COLUMN];
214 - (void)applicationWillTerminate:(NSNotification *)notification
216     /* 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 */
217     [self saveTableColumns];
220 - (void)initStrings
222     [_playPlaylistMenuItem setTitle: _NS("Play")];
223     [_deletePlaylistMenuItem setTitle: _NS("Delete")];
224     [_recursiveExpandPlaylistMenuItem setTitle: _NS("Expand All")];
225     [_recursiveCollapsePlaylistMenuItem setTitle: _NS("Collapse All")];
226     [_selectAllPlaylistMenuItem setTitle: _NS("Select All")];
227     [_infoPlaylistMenuItem setTitle: _NS("Media Information...")];
228     [_revealInFinderPlaylistMenuItem setTitle: _NS("Reveal in Finder")];
229     [_addFilesToPlaylistMenuItem setTitle: _NS("Add File...")];
232 - (void)playlistUpdated
234     [_outlineView reloadData];
237 - (void)playbackModeUpdated
239     [_model playbackModeUpdated];
243 - (BOOL)isSelectionEmpty
245     return [_outlineView selectedRow] == -1;
248 - (void)currentlyPlayingItemChanged
250     VLCPLItem *item = [[self model] currentlyPlayingItem];
251     if (!item)
252         return;
254     // Search for item row for selection
255     NSInteger itemIndex = [_outlineView rowForItem:item];
256     if (itemIndex < 0) {
257         // Expand if needed. This must be done from root to child
258         // item in order to work
259         NSMutableArray *itemsToExpand = [NSMutableArray array];
260         VLCPLItem *tmpItem = [item parent];
261         while (tmpItem != nil) {
262             [itemsToExpand addObject:tmpItem];
263             tmpItem = [tmpItem parent];
264         }
266         for(int i = (int)itemsToExpand.count - 1; i >= 0; i--) {
267             VLCPLItem *currentItem = [itemsToExpand objectAtIndex:i];
268             [_outlineView expandItem: currentItem];
269         }
270     }
272     // Update highlight for currently playing item
273     [_outlineView reloadData];
275     // Search for row again
276     itemIndex = [_outlineView rowForItem:item];
277     if (itemIndex < 0) {
278         return;
279     }
281     [_outlineView selectRowIndexes: [NSIndexSet indexSetWithIndex: itemIndex] byExtendingSelection: NO];
282     [_outlineView scrollRowToVisible: itemIndex];
285 #pragma mark -
286 #pragma mark Playlist actions
288 /* When called retrieves the selected outlineview row and plays that node or item */
289 - (IBAction)playItem:(id)sender
291     playlist_t *p_playlist = pl_Get(getIntf());
293     // ignore clicks on column header when handling double action
294     if (sender == _outlineView && [_outlineView clickedRow] == -1)
295         return;
297     VLCPLItem *o_item = [_outlineView itemAtRow:[_outlineView selectedRow]];
298     if (!o_item)
299         return;
301     PL_LOCK;
302     playlist_item_t *p_item = playlist_ItemGetById(p_playlist, [o_item plItemId]);
303     playlist_item_t *p_node = playlist_ItemGetById(p_playlist, [[[self model] rootItem] plItemId]);
305     if (p_item && p_node) {
306         playlist_ViewPlay(p_playlist, p_node, p_item);
307     }
308     PL_UNLOCK;
311 - (IBAction)revealItemInFinder:(id)sender
313     NSIndexSet *selectedRows = [_outlineView selectedRowIndexes];
314     if (selectedRows.count < 1)
315         return;
317     VLCPLItem *o_item = [_outlineView itemAtRow:selectedRows.firstIndex];
319     char *psz_url = input_item_GetURI([o_item input]);
320     if (!psz_url)
321         return;
322     char *psz_path = vlc_uri2path(psz_url);
323     NSString *path = toNSStr(psz_path);
324     free(psz_url);
325     free(psz_path);
327     msg_Dbg(getIntf(), "Reveal url %s in finder", [path UTF8String]);
328     [[NSWorkspace sharedWorkspace] selectFile: path inFileViewerRootedAtPath: path];
331 - (IBAction)selectAll:(id)sender
333     [_outlineView selectAll: nil];
336 - (IBAction)showInfoPanel:(id)sender
338     [[[VLCMain sharedInstance] currentMediaInfoPanel] toggleWindow:sender];
341 - (IBAction)addFilesToPlaylist:(id)sender
343     NSIndexSet *selectedRows = [_outlineView selectedRowIndexes];
345     int position = -1;
346     VLCPLItem *parentItem = [[self model] rootItem];
348     if (selectedRows.count >= 1) {
349         position = (int)selectedRows.firstIndex + 1;
350         parentItem = [_outlineView itemAtRow:selectedRows.firstIndex];
351         if ([parentItem parent] != nil)
352             parentItem = [parentItem parent];
353     }
355     [[[VLCMain sharedInstance] open] openFileWithAction:^(NSArray *files) {
356         [self addPlaylistItems:files
357               withParentItemId:[parentItem plItemId]
358                          atPos:position
359                  startPlayback:NO];
360     }];
363 - (IBAction)deleteItem:(id)sender
365     [_model deleteSelectedItem];
368 // Actions for playlist column selections
371 - (void)togglePlaylistColumnTable:(id)sender
373     NSInteger i_new_state = ![sender state];
374     NSInteger i_tag = [sender tag];
376     NSString *column = [_menuOrderOfPlaylistTableColumns objectAtIndex:i_tag];
378     [self setPlaylistColumnTableState:i_new_state forColumn:column];
381 - (BOOL)setPlaylistColumnTableState:(NSInteger)i_state forColumn:(NSString *)columnId
383     NSUInteger i_tag = [_menuOrderOfPlaylistTableColumns indexOfObject: columnId];
384     // prevent setting unknown columns
385     if(i_tag == NSNotFound)
386         return NO;
388     // update state of menu items
389     [[[_playlistHeaderView menu] itemWithTag: i_tag] setState: i_state];
390     [[[[[VLCMain sharedInstance] mainMenu] playlistTableColumnsMenu] itemWithTag: i_tag] setState: i_state];
392     // Change outline view
393     if (i_state == NSOnState) {
394         NSString *title = [_translationsForPlaylistTableColumns objectForKey:columnId];
395         if (!title)
396             return NO;
398         NSTableColumn *tableColumn = [[NSTableColumn alloc] initWithIdentifier:columnId];
399         [tableColumn setEditable:NO];
400         [[tableColumn dataCell] setFont:[NSFont controlContentFontOfSize:11.]];
402         [[tableColumn headerCell] setStringValue:[_translationsForPlaylistTableColumns objectForKey:columnId]];
404         if ([columnId isEqualToString: TRACKNUM_COLUMN]) {
405             [tableColumn setMinWidth:20.];
406             [tableColumn setMaxWidth:70.];
407             [[tableColumn headerCell] setStringValue:@"#"];
409         } else {
410             [tableColumn setMinWidth:42.];
411         }
413         [_outlineView addTableColumn:tableColumn];
414         [_outlineView reloadData];
415         [_outlineView setNeedsDisplay: YES];
416     }
417     else
418         [_outlineView removeTableColumn: [_outlineView tableColumnWithIdentifier:columnId]];
420     [_outlineView setOutlineTableColumn: [_outlineView tableColumnWithIdentifier:TITLE_COLUMN]];
422     return YES;
425 - (BOOL)validateMenuItem:(NSMenuItem *)item
427     if ([item action] == @selector(revealItemInFinder:)) {
428         NSIndexSet *selectedRows = [_outlineView selectedRowIndexes];
429         if (selectedRows.count != 1)
430             return NO;
432         VLCPLItem *o_item = [_outlineView itemAtRow:selectedRows.firstIndex];
434         // Check if item exists in file system
435         char *psz_url = input_item_GetURI([o_item input]);
436         NSURL *url = [NSURL URLWithString:toNSStr(psz_url)];
437         free(psz_url);
438         if (![url isFileURL])
439             return NO;
440         if (![[NSFileManager defaultManager] fileExistsAtPath:[url path]])
441             return NO;
443     } else if ([item action] == @selector(deleteItem:)) {
444         return [_outlineView numberOfSelectedRows] > 0 && _model.editAllowed;
445     } else if ([item action] == @selector(selectAll:)) {
446         return [_outlineView numberOfRows] > 0;
447     } else if ([item action] == @selector(playItem:)) {
448         return [_outlineView numberOfSelectedRows] > 0;
449     } else if ([item action] == @selector(recursiveExpandOrCollapseNode:)) {
450         return [_outlineView numberOfSelectedRows] > 0;
451     } else if ([item action] == @selector(showInfoPanel:)) {
452         return [_outlineView numberOfSelectedRows] > 0;
453     }
455     return YES;
458 #pragma mark -
459 #pragma mark Helper for playlist table columns
461 - (void)setupPlaylistTableColumnsForMenu:(NSMenu *)menu
463     NSMenuItem *menuItem;
464     NSUInteger count = [_menuOrderOfPlaylistTableColumns count];
465     for (NSUInteger i = 0; i < count; i++) {
466         NSString *columnId = [_menuOrderOfPlaylistTableColumns objectAtIndex:i];
467         NSString *title = [_translationsForPlaylistTableColumns objectForKey:columnId];
468         menuItem = [menu addItemWithTitle:title
469                                    action:@selector(togglePlaylistColumnTable:)
470                             keyEquivalent:@""];
471         [menuItem setTarget:self];
472         [menuItem setTag:i];
474         /* don't set a valid action for the title column selector, since we want it to be disabled */
475         if ([columnId isEqualToString: TITLE_COLUMN])
476             [menuItem setAction:nil];
478     }
481 - (void)saveTableColumns
483     NSMutableArray *arrayToSave = [[NSMutableArray alloc] init];
484     NSArray *columns = [[NSArray alloc] initWithArray:[_outlineView tableColumns]];
485     NSUInteger columnCount = [columns count];
486     NSTableColumn *currentColumn;
487     for (NSUInteger i = 0; i < columnCount; i++) {
488         currentColumn = [columns objectAtIndex:i];
489         [arrayToSave addObject:[NSArray arrayWithObjects:[currentColumn identifier], [NSNumber numberWithFloat:[currentColumn width]], nil]];
490     }
491     [[NSUserDefaults standardUserDefaults] setObject:arrayToSave forKey:@"PlaylistColumnSelection"];
492     [[NSUserDefaults standardUserDefaults] synchronize];
495 #pragma mark -
496 #pragma mark Item helpers
498 - (input_item_t *)createItem:(NSDictionary *)itemToCreateDict
500     intf_thread_t *p_intf = getIntf();
502     input_item_t *p_input;
503     BOOL b_rem = FALSE, b_dir = FALSE, b_writable = FALSE;
504     NSString *uri, *name, *path;
505     NSURL * url;
506     NSArray *optionsArray;
508     /* Get the item */
509     uri = (NSString *)[itemToCreateDict objectForKey: @"ITEM_URL"];
510     url = [NSURL URLWithString: uri];
511     path = [url path];
512     name = (NSString *)[itemToCreateDict objectForKey: @"ITEM_NAME"];
513     optionsArray = (NSArray *)[itemToCreateDict objectForKey: @"ITEM_OPTIONS"];
515     if ([[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&b_dir] && b_dir &&
516         [[NSWorkspace sharedWorkspace] getFileSystemInfoForPath:path isRemovable: &b_rem
517                                                      isWritable:&b_writable isUnmountable:NULL description:NULL type:NULL] && b_rem && !b_writable && [url isFileURL]) {
519         NSString *diskType = [[VLCStringUtility sharedInstance] getVolumeTypeFromMountPath: path];
520         msg_Dbg(p_intf, "detected optical media of type %s in the file input", [diskType UTF8String]);
522         if ([diskType isEqualToString: kVLCMediaDVD])
523             uri = [NSString stringWithFormat: @"dvdnav://%@", [[VLCStringUtility sharedInstance] getBSDNodeFromMountPath: path]];
524         else if ([diskType isEqualToString: kVLCMediaVideoTSFolder])
525             uri = [NSString stringWithFormat: @"dvdnav://%@", path];
526         else if ([diskType isEqualToString: kVLCMediaAudioCD])
527             uri = [NSString stringWithFormat: @"cdda://%@", [[VLCStringUtility sharedInstance] getBSDNodeFromMountPath: path]];
528         else if ([diskType isEqualToString: kVLCMediaVCD])
529             uri = [NSString stringWithFormat: @"vcd://%@#0:0", [[VLCStringUtility sharedInstance] getBSDNodeFromMountPath: path]];
530         else if ([diskType isEqualToString: kVLCMediaSVCD])
531             uri = [NSString stringWithFormat: @"vcd://%@@0:0", [[VLCStringUtility sharedInstance] getBSDNodeFromMountPath: path]];
532         else if ([diskType isEqualToString: kVLCMediaBD] || [diskType isEqualToString: kVLCMediaBDMVFolder])
533             uri = [NSString stringWithFormat: @"bluray://%@", path];
534         else
535             msg_Warn(getIntf(), "unknown disk type, treating %s as regular input", [path UTF8String]);
537         p_input = input_item_New([uri UTF8String], [[[NSFileManager defaultManager] displayNameAtPath:path] UTF8String]);
538     }
539     else
540         p_input = input_item_New([uri fileSystemRepresentation], name ? [name UTF8String] : NULL);
542     if (!p_input)
543         return NULL;
545     if (optionsArray) {
546         NSUInteger count = [optionsArray count];
547         for (NSUInteger i = 0; i < count; i++)
548             input_item_AddOption(p_input, [[optionsArray objectAtIndex:i] UTF8String], VLC_INPUT_OPTION_TRUSTED);
549     }
551     /* Recent documents menu */
552     if (url != nil && var_InheritBool(getIntf(), "macosx-recentitems"))
553         [[NSDocumentController sharedDocumentController] noteNewRecentDocumentURL:url];
555     return p_input;
558 - (NSArray *)createItemsFromExternalPasteboard:(NSPasteboard *)pasteboard
560     NSArray *o_array = [NSArray array];
561     if (![[pasteboard types] containsObject: NSFilenamesPboardType])
562         return o_array;
564     NSArray *o_values = [[pasteboard propertyListForType: NSFilenamesPboardType] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
565     NSUInteger count = [o_values count];
567     for (NSUInteger i = 0; i < count; i++) {
568         NSDictionary *o_dic;
569         char *psz_uri = vlc_path2uri([[o_values objectAtIndex:i] UTF8String], NULL);
570         if (!psz_uri)
571             continue;
573         o_dic = [NSDictionary dictionaryWithObject:toNSStr(psz_uri) forKey:@"ITEM_URL"];
574         free(psz_uri);
576         o_array = [o_array arrayByAddingObject: o_dic];
577     }
579     return o_array;
582 - (void)addPlaylistItems:(NSArray*)array
585     int i_plItemId = -1;
587     BOOL b_autoplay = var_InheritBool(getIntf(), "macosx-autoplay");
589     [self addPlaylistItems:array withParentItemId:i_plItemId atPos:-1 startPlayback:b_autoplay];
592 - (void)addPlaylistItems:(NSArray*)array tryAsSubtitle:(BOOL)isSubtitle
594     input_thread_t *p_input = pl_CurrentInput(getIntf());
595     if (isSubtitle && array.count == 1 && p_input) {
596         int i_result = input_AddSlave(p_input, SLAVE_TYPE_SPU,
597                     [[[array firstObject] objectForKey:@"ITEM_URL"] UTF8String],
598                     true, true, true);
599         if (i_result == VLC_SUCCESS) {
600             vlc_object_release(p_input);
601             return;
602         }
603     }
605     if (p_input)
606         vlc_object_release(p_input);
608     [self addPlaylistItems:array];
611 - (void)addPlaylistItems:(NSArray*)array withParentItemId:(int)i_plItemId atPos:(int)i_position startPlayback:(BOOL)b_start
613     playlist_t * p_playlist = pl_Get(getIntf());
614     PL_LOCK;
616     playlist_item_t *p_parent = NULL;
617     if (i_plItemId >= 0)
618         p_parent = playlist_ItemGetById(p_playlist, i_plItemId);
619     else
620         p_parent = p_playlist->p_playing;
622     if (!p_parent) {
623         PL_UNLOCK;
624         return;
625     }
627     NSUInteger count = [array count];
628     int i_current_offset = 0;
629     for (NSUInteger i = 0; i < count; ++i) {
631         NSDictionary *o_current_item = [array objectAtIndex:i];
632         input_item_t *p_input = [self createItem: o_current_item];
633         if (!p_input)
634             continue;
636         int i_pos = (i_position == -1) ? PLAYLIST_END : i_position + i_current_offset++;
637         playlist_item_t *p_item = playlist_NodeAddInput(p_playlist, p_input,
638                                                         p_parent, i_pos);
639         if (!p_item)
640             continue;
642         if (i == 0 && b_start) {
643             playlist_ViewPlay(p_playlist, p_parent, p_item);
644         }
645         input_item_Release(p_input);
646     }
647     PL_UNLOCK;
650 - (IBAction)recursiveExpandOrCollapseNode:(id)sender
652     bool expand = (sender == _recursiveExpandPlaylistMenuItem);
654     NSIndexSet * selectedRows = [_outlineView selectedRowIndexes];
655     NSUInteger count = [selectedRows count];
656     NSUInteger indexes[count];
657     [selectedRows getIndexes:indexes maxCount:count inIndexRange:nil];
659     id item;
660     for (NSUInteger i = 0; i < count; i++) {
661         item = [_outlineView itemAtRow: indexes[i]];
663         /* We need to collapse the node first, since OSX refuses to recursively
664          expand an already expanded node, even if children nodes are collapsed. */
665         if ([_outlineView isExpandable:item]) {
666             [_outlineView collapseItem: item collapseChildren: YES];
668             if (expand)
669                 [_outlineView expandItem: item expandChildren: YES];
670         }
672         selectedRows = [_outlineView selectedRowIndexes];
673         [selectedRows getIndexes:indexes maxCount:count inIndexRange:nil];
674     }
677 - (NSMenu *)menuForEvent:(NSEvent *)o_event
679     if (!b_playlistmenu_nib_loaded)
680         b_playlistmenu_nib_loaded = [[NSBundle mainBundle] loadNibNamed:@"PlaylistMenu" owner:self topLevelObjects:nil];
682     NSPoint pt = [_outlineView convertPoint: [o_event locationInWindow] fromView: nil];
683     NSInteger row = [_outlineView rowAtPoint:pt];
684     if (row != -1 && ![[_outlineView selectedRowIndexes] containsIndex: row])
685         [_outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO];
687     // TODO Reenable once per-item info panel is supported again
688     _infoPlaylistMenuItem.hidden = YES;
690     return _playlistMenu;
693 - (void)outlineView:(NSOutlineView *)outlineView didClickTableColumn:(NSTableColumn *)aTableColumn
695     int type = 0;
696     NSString * identifier = [aTableColumn identifier];
698     if (_sortTableColumn == aTableColumn)
699         b_isSortDescending = !b_isSortDescending;
700     else
701         b_isSortDescending = false;
703     if (b_isSortDescending)
704         type = ORDER_REVERSE;
705     else
706         type = ORDER_NORMAL;
708     [[self model] sortForColumn:identifier withMode:type];
710     /* Clear indications of any existing column sorting */
711     NSUInteger count = [[_outlineView tableColumns] count];
712     for (NSUInteger i = 0 ; i < count ; i++)
713         [_outlineView setIndicatorImage:nil inTableColumn: [[_outlineView tableColumns] objectAtIndex:i]];
715     [_outlineView setHighlightedTableColumn:nil];
716     _sortTableColumn = aTableColumn;
717     [_outlineView setHighlightedTableColumn:aTableColumn];
719     if (b_isSortDescending)
720         [_outlineView setIndicatorImage:_descendingSortingImage inTableColumn:aTableColumn];
721     else
722         [_outlineView setIndicatorImage:_ascendingSortingImage inTableColumn:aTableColumn];
726 - (void)outlineView:(NSOutlineView *)outlineView
727     willDisplayCell:(id)cell
728      forTableColumn:(NSTableColumn *)tableColumn
729                item:(id)item
731     /* this method can be called when VLC is already dead, hence the extra checks */
732     intf_thread_t * p_intf = getIntf();
733     if (!p_intf)
734         return;
735     playlist_t *p_playlist = pl_Get(p_intf);
737     NSFont *fontToUse;
738     if (var_InheritBool(getIntf(), "macosx-large-text"))
739         fontToUse = [NSFont systemFontOfSize:13.];
740     else
741         fontToUse = [NSFont systemFontOfSize:11.];
743     BOOL b_is_playing = NO;
744     PL_LOCK;
745     playlist_item_t *p_current_item = playlist_CurrentPlayingItem(p_playlist);
746     if (p_current_item) {
747         b_is_playing = p_current_item->i_id == [item plItemId];
748     }
749     PL_UNLOCK;
751     if (b_is_playing)
752         [cell setFont: [[NSFontManager sharedFontManager] convertFont:fontToUse toHaveTrait:NSBoldFontMask]];
753     else
754         [cell setFont: [[NSFontManager sharedFontManager] convertFont:fontToUse toNotHaveTrait:NSBoldFontMask]];
757 // TODO remove method
758 - (NSArray *)draggedItems
760     return [[self model] draggedItems];
763 @end