1 /*****************************************************************************
2 * playlist.m: MacOS X interface module
3 *****************************************************************************
4 * Copyright (C) 2002-2014 VLC authors and VideoLAN
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>
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.
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.
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 *****************************************************************************/
28 * add 'icons' for different types of nodes? (http://www.cocoadev.com/index.pl?IconAndTextInTableCell)
29 * reimplement enable/disable item
33 /*****************************************************************************
35 *****************************************************************************/
36 #include <stdlib.h> /* malloc(), free() */
37 #include <sys/param.h> /* for MAXPATHLEN */
40 #include <sys/mount.h>
42 #import "CompatibilityFixes.h"
47 #import "playlistinfo.h"
53 #import "CoreInteraction.h"
56 #import <vlc_interface.h>
59 /*****************************************************************************
60 * VLCPlaylistView implementation
61 *****************************************************************************/
62 @implementation VLCPlaylistView
64 - (NSMenu *)menuForEvent:(NSEvent *)o_event
66 return([(VLCPlaylist *)[self delegate] menuForEvent: o_event]);
69 - (void)keyDown:(NSEvent *)o_event
73 if ([[o_event characters] length])
74 key = [[o_event characters] characterAtIndex: 0];
77 case NSDeleteCharacter:
78 case NSDeleteFunctionKey:
79 case NSDeleteCharFunctionKey:
80 case NSBackspaceCharacter:
81 [(VLCPlaylist *)[self delegate] deleteItem:self];
84 case NSEnterCharacter:
85 case NSCarriageReturnCharacter:
86 [(VLCPlaylist *)[[VLCMain sharedInstance] playlist] playItem:nil];
90 [super keyDown: o_event];
95 - (BOOL)validateMenuItem:(NSMenuItem *)item
97 if (([self numberOfSelectedRows] >= 1 && [item action] == @selector(delete:)) || [item action] == @selector(selectAll:))
103 - (BOOL)acceptsFirstResponder
108 - (BOOL)becomeFirstResponder
110 [self setNeedsDisplay:YES];
114 - (BOOL)resignFirstResponder
116 [self setNeedsDisplay:YES];
120 - (IBAction)delete:(id)sender
122 [[[VLCMain sharedInstance] playlist] deleteItem: sender];
127 /*****************************************************************************
128 * VLCPlaylistWizard implementation
129 *****************************************************************************/
130 @implementation VLCPlaylistWizard
132 - (IBAction)reloadOutlineView
134 /* Only reload the outlineview if the wizard window is open since this can
135 be quite long on big playlists */
143 /*****************************************************************************
144 * An extension to NSOutlineView's interface to fix compilation warnings
145 * and let us access these 2 functions properly.
146 * This uses a private API, but works fine on all current OSX releases.
147 * Radar ID 11739459 request a public API for this. However, it is probably
148 * easier and faster to recreate similar looking bitmaps ourselves.
149 *****************************************************************************/
151 @interface NSOutlineView (UndocumentedSortImages)
152 + (NSImage *)_defaultTableHeaderSortImage;
153 + (NSImage *)_defaultTableHeaderReverseSortImage;
157 /*****************************************************************************
158 * VLCPlaylist implementation
159 *****************************************************************************/
160 @interface VLCPlaylist ()
162 playlist_item_t * p_current_root_item;
164 NSImage *o_descendingSortingImage;
165 NSImage *o_ascendingSortingImage;
167 NSMutableArray *o_nodes_array;
168 NSMutableArray *o_items_array;
170 BOOL b_selected_item_met;
171 BOOL b_isSortDescending;
173 NSUInteger retainedRowSelection;
175 BOOL b_playlistmenu_nib_loaded;
179 - (void)saveTableColumns;
182 @implementation VLCPlaylist
186 NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
187 NSMutableArray *o_columnArray = [[NSMutableArray alloc] init];
188 [o_columnArray addObject: [NSArray arrayWithObjects:TITLE_COLUMN, [NSNumber numberWithFloat:190.], nil]];
189 [o_columnArray addObject: [NSArray arrayWithObjects:ARTIST_COLUMN, [NSNumber numberWithFloat:95.], nil]];
190 [o_columnArray addObject: [NSArray arrayWithObjects:DURATION_COLUMN, [NSNumber numberWithFloat:95.], nil]];
192 NSDictionary *appDefaults = [NSDictionary dictionaryWithObjectsAndKeys:
193 [NSArray arrayWithArray:o_columnArray], @"PlaylistColumnSelection",
194 [NSArray array], @"recentlyPlayedMediaList",
195 [NSDictionary dictionary], @"recentlyPlayedMedia", nil];
197 [defaults registerDefaults:appDefaults];
198 [o_columnArray release];
201 - (playlist_item_t *)currentPlaylistRoot
204 playlist_t *p_playlist = pl_Get(VLCIntf);
205 return p_playlist->p_playing;
217 if (config_GetInt(VLCIntf, "macosx-large-text")) {
218 fontToUse = [NSFont systemFontOfSize:13.];
221 fontToUse = [NSFont systemFontOfSize:11.];
225 NSArray *columns = [o_outline_view tableColumns];
226 NSUInteger count = columns.count;
227 for (NSUInteger x = 0; x < count; x++)
228 [[[columns objectAtIndex:x] dataCell] setFont:fontToUse];
229 [o_outline_view setRowHeight:rowHeight];
238 playlist_t * p_playlist = pl_Get(VLCIntf);
239 p_current_root_item = p_playlist->p_local_category;
240 o_outline_dict = [[NSMutableDictionary alloc] init];
242 o_nodes_array = [[NSMutableArray alloc] init];
243 o_items_array = [[NSMutableArray alloc] init];
250 [o_outline_dict release];
251 [o_nodes_array release];
252 [o_items_array release];
261 playlist_t * p_playlist = pl_Get(VLCIntf);
262 [o_outline_view setTarget: self];
263 [o_outline_view setDelegate: self];
264 [o_outline_view setAllowsEmptySelection: NO];
265 [o_outline_view expandItem: [o_outline_view itemAtRow:0]];
270 o_model = [[PLModel alloc] initWithOutlineView:o_outline_view playlist:p_playlist rootItem:p_current_root_item];
271 [o_outline_view setDataSource:o_model];
272 [o_outline_view reloadData];
274 [o_outline_view setDoubleAction: @selector(playItem:)];
276 [o_outline_view registerForDraggedTypes: [NSArray arrayWithObjects:NSFilenamesPboardType, @"VLCPlaylistItemPboardType", nil]];
277 [o_outline_view setIntercellSpacing: NSMakeSize (0.0, 1.0)];
279 /* This uses a private API, but works fine on all current OSX releases.
280 * Radar ID 11739459 request a public API for this. However, it is probably
281 * easier and faster to recreate similar looking bitmaps ourselves. */
282 o_ascendingSortingImage = [[NSOutlineView class] _defaultTableHeaderSortImage];
283 o_descendingSortingImage = [[NSOutlineView class] _defaultTableHeaderReverseSortImage];
285 o_tc_sortColumn = nil;
287 NSArray * o_columnArray = [[NSUserDefaults standardUserDefaults] arrayForKey:@"PlaylistColumnSelection"];
288 NSUInteger count = [o_columnArray count];
290 id o_menu = [[VLCMain sharedInstance] mainMenu];
293 NSMenu *o_context_menu = [o_menu setupPlaylistTableColumnsMenu];
294 [o_playlist_header setMenu: o_context_menu];
296 for (NSUInteger i = 0; i < count; i++) {
297 o_column = [[o_columnArray objectAtIndex:i] objectAtIndex:0];
298 if ([o_column isEqualToString:@"status"])
301 if(![o_menu setPlaylistColumnTableState: NSOnState forColumn: o_column])
304 [[o_outline_view tableColumnWithIdentifier: o_column] setWidth: [[[o_columnArray objectAtIndex:i] objectAtIndex:1] floatValue]];
307 [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(applicationWillTerminate:) name: NSApplicationWillTerminateNotification object: nil];
312 - (void)applicationWillTerminate:(NSNotification *)notification
314 /* 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 */
315 [self saveTableColumns];
320 [o_mi_play setTitle: _NS("Play")];
321 [o_mi_delete setTitle: _NS("Delete")];
322 [o_mi_recursive_expand setTitle: _NS("Expand Node")];
323 [o_mi_selectall setTitle: _NS("Select All")];
324 [o_mi_info setTitle: _NS("Media Information...")];
325 [o_mi_dl_cover_art setTitle: _NS("Download Cover Art")];
326 [o_mi_preparse setTitle: _NS("Fetch Meta Data")];
327 [o_mi_revealInFinder setTitle: _NS("Reveal in Finder")];
328 [o_mm_mi_revealInFinder setTitle: _NS("Reveal in Finder")];
329 [[o_mm_mi_revealInFinder menu] setAutoenablesItems: NO];
330 [o_mi_sort_name setTitle: _NS("Sort Node by Name")];
331 [o_mi_sort_author setTitle: _NS("Sort Node by Author")];
333 [o_search_field setToolTip: _NS("Search in Playlist")];
336 - (void)playlistUpdated
338 /* Clear indications of any existing column sorting */
339 NSUInteger count = [[o_outline_view tableColumns] count];
340 for (NSUInteger i = 0 ; i < count ; i++)
341 [o_outline_view setIndicatorImage:nil inTableColumn: [[o_outline_view tableColumns] objectAtIndex:i]];
343 [o_outline_view setHighlightedTableColumn:nil];
344 o_tc_sortColumn = nil;
345 // TODO Find a way to keep the dict size to a minimum
346 //[o_outline_dict removeAllObjects];
347 [o_outline_view reloadData];
348 [[[[VLCMain sharedInstance] wizard] playlistWizard] reloadOutlineView];
350 [o_outline_view selectRowIndexes:[NSIndexSet indexSetWithIndex:retainedRowSelection] byExtendingSelection:NO];
352 [self outlineViewSelectionDidChange: nil];
353 [[VLCMain sharedInstance] updateMainWindow];
356 - (void)outlineViewSelectionDidChange:(NSNotification *)notification
359 // playlist_item_t * p_item = [[o_outline_view itemAtRow:[o_outline_view selectedRow]] pointerValue];
362 // /* update the state of our Reveal-in-Finder menu items */
363 // NSMutableString *o_mrl;
364 // char *psz_uri = input_item_GetURI(p_item->p_input);
366 // [o_mi_revealInFinder setEnabled: NO];
367 // [o_mm_mi_revealInFinder setEnabled: NO];
369 // o_mrl = [NSMutableString stringWithUTF8String: psz_uri];
371 // /* perform some checks whether it is a file and if it is local at all... */
372 // NSRange prefix_range = [o_mrl rangeOfString: @"file:"];
373 // if (prefix_range.location != NSNotFound)
374 // [o_mrl deleteCharactersInRange: prefix_range];
376 // if ([o_mrl characterAtIndex:0] == '/') {
377 // [o_mi_revealInFinder setEnabled: YES];
378 // [o_mm_mi_revealInFinder setEnabled: YES];
383 // /* update our info-panel to reflect the new item */
384 // [[[VLCMain sharedInstance] info] updatePanelWithItem:p_item->p_input];
388 - (BOOL)isSelectionEmpty
390 return [o_outline_view selectedRow] == -1;
393 - (void)updateRowSelection
396 playlist_t *p_playlist = pl_Get(VLCIntf);
397 playlist_item_t *p_item, *p_temp_item;
398 NSMutableArray *o_array = [NSMutableArray array];
401 p_item = playlist_CurrentPlayingItem(p_playlist);
402 if (p_item == NULL) {
407 p_temp_item = p_item;
408 while(p_temp_item->p_parent) {
409 [o_array insertObject: [NSValue valueWithPointer: p_temp_item] atIndex: 0];
410 p_temp_item = p_temp_item->p_parent;
414 NSUInteger count = [o_array count];
415 for (NSUInteger j = 0; j < count - 1; j++) {
417 if ((o_item = [o_outline_dict objectForKey:
418 [NSString stringWithFormat: @"%p",
419 [[o_array objectAtIndex:j] pointerValue]]]) != nil) {
420 [o_outline_view expandItem: o_item];
424 id o_item = [o_outline_dict objectForKey:[NSString stringWithFormat: @"%p", p_item]];
425 NSInteger i_index = [o_outline_view rowForItem:o_item];
426 [o_outline_view selectRowIndexes:[NSIndexSet indexSetWithIndex:i_index] byExtendingSelection:NO];
427 [o_outline_view setNeedsDisplay:YES];
430 /* Check if p_item is a child of p_node recursively. We need to check the item
431 existence first since OSX sometimes tries to redraw items that have been
432 deleted. We don't do it when not required since this verification takes
433 quite a long time on big playlists (yes, pretty hacky). */
435 // todo remove useless parameters
436 - (BOOL)isItem: (PLItem *)p_item inNode: (PLItem *)p_node checkItemExistence:(BOOL)b_check locked:(BOOL)b_locked
438 PLItem *p_temp_item = p_item;
440 if ([p_node plItemId] == [p_item plItemId])
444 p_temp_item = [p_temp_item parent];
445 if ([p_temp_item plItemId] == [p_node plItemId]) {
453 /* This method is useful for instance to remove the selected children of an
454 already selected node */
455 - (void)removeItemsFrom:(id)o_items ifChildrenOf:(id)o_nodes
457 NSUInteger itemCount = [o_items count];
458 NSUInteger nodeCount = [o_nodes count];
459 for (NSUInteger i = 0 ; i < itemCount ; i++) {
460 for (NSUInteger j = 0 ; j < nodeCount ; j++) {
461 if (o_items == o_nodes) {
462 if (j == i) continue;
464 if ([self isItem: [o_items objectAtIndex:i]
465 inNode: [o_nodes objectAtIndex:j]
466 checkItemExistence: NO locked:NO]) {
467 [o_items removeObjectAtIndex:i];
468 /* We need to execute the next iteration with the same index
469 since the current item has been deleted */
477 - (IBAction)savePlaylist:(id)sender
479 playlist_t * p_playlist = pl_Get(VLCIntf);
481 NSSavePanel *o_save_panel = [NSSavePanel savePanel];
482 NSString * o_name = [NSString stringWithFormat: @"%@", _NS("Untitled")];
484 [NSBundle loadNibNamed:@"PlaylistAccessoryView" owner:self];
486 [o_save_accessory_text setStringValue: _NS("File Format:")];
487 [[o_save_accessory_popup itemAtIndex:0] setTitle: _NS("Extended M3U")];
488 [[o_save_accessory_popup itemAtIndex:1] setTitle: _NS("XML Shareable Playlist Format (XSPF)")];
489 [[o_save_accessory_popup itemAtIndex:2] setTitle: _NS("HTML playlist")];
491 [o_save_panel setTitle: _NS("Save Playlist")];
492 [o_save_panel setPrompt: _NS("Save")];
493 [o_save_panel setAccessoryView: o_save_accessory_view];
494 [o_save_panel setNameFieldStringValue: o_name];
496 if ([o_save_panel runModal] == NSFileHandlingPanelOKButton) {
497 NSString *o_filename = [[o_save_panel URL] path];
499 if ([o_save_accessory_popup indexOfSelectedItem] == 0) {
500 NSString * o_real_filename;
502 range.location = [o_filename length] - [@".m3u" length];
503 range.length = [@".m3u" length];
505 if ([o_filename compare:@".m3u" options: NSCaseInsensitiveSearch range: range] != NSOrderedSame)
506 o_real_filename = [NSString stringWithFormat: @"%@.m3u", o_filename];
508 o_real_filename = o_filename;
510 playlist_Export(p_playlist,
511 [o_real_filename fileSystemRepresentation],
512 p_playlist->p_local_category, "export-m3u");
513 } else if ([o_save_accessory_popup indexOfSelectedItem] == 1) {
514 NSString * o_real_filename;
516 range.location = [o_filename length] - [@".xspf" length];
517 range.length = [@".xspf" length];
519 if ([o_filename compare:@".xspf" options: NSCaseInsensitiveSearch range: range] != NSOrderedSame)
520 o_real_filename = [NSString stringWithFormat: @"%@.xspf", o_filename];
522 o_real_filename = o_filename;
524 playlist_Export(p_playlist,
525 [o_real_filename fileSystemRepresentation],
526 p_playlist->p_local_category, "export-xspf");
528 NSString * o_real_filename;
530 range.location = [o_filename length] - [@".html" length];
531 range.length = [@".html" length];
533 if ([o_filename compare:@".html" options: NSCaseInsensitiveSearch range: range] != NSOrderedSame)
534 o_real_filename = [NSString stringWithFormat: @"%@.html", o_filename];
536 o_real_filename = o_filename;
538 playlist_Export(p_playlist,
539 [o_real_filename fileSystemRepresentation],
540 p_playlist->p_local_category, "export-html");
545 /* When called retrieves the selected outlineview row and plays that node or item */
546 - (IBAction)playItem:(id)sender
548 intf_thread_t * p_intf = VLCIntf;
549 playlist_t * p_playlist = pl_Get(p_intf);
551 playlist_item_t *p_item;
552 playlist_item_t *p_node = NULL;
554 // ignore clicks on column header when handling double action
555 if (sender != nil && [o_outline_view clickedRow] == -1 && sender != o_mi_play)
559 PLItem *o_item = [o_outline_view itemAtRow:[o_outline_view selectedRow]];
560 p_item = playlist_ItemGetById(p_playlist, [o_item plItemId]);
563 if (p_item->i_children == -1) {
564 p_node = p_item->p_parent;
567 if (p_node->i_children > 0 && p_node->pp_children[0]->i_children == -1)
568 p_item = p_node->pp_children[0];
573 playlist_Control(p_playlist, PLAYLIST_VIEWPLAY, pl_Locked, p_node, p_item);
578 - (IBAction)revealItemInFinder:(id)sender
580 NSIndexSet * selectedRows = [o_outline_view selectedRowIndexes];
581 NSUInteger count = [selectedRows count];
582 NSUInteger indexes[count];
583 [selectedRows getIndexes:indexes maxCount:count inIndexRange:nil];
585 NSMutableString * o_mrl;
586 for (NSUInteger i = 0; i < count; i++) {
587 PLItem *o_item = [o_outline_view itemAtRow:indexes[i]];
589 char * psz_url = decode_URI(input_item_GetURI([o_item input]));
590 o_mrl = [[NSMutableString alloc] initWithString: [NSString stringWithUTF8String:psz_url ? psz_url : ""]];
594 /* perform some checks whether it is a file and if it is local at all... */
595 if ([o_mrl length] > 0) {
596 NSRange prefix_range = [o_mrl rangeOfString: @"file:"];
597 if (prefix_range.location != NSNotFound)
598 [o_mrl deleteCharactersInRange: prefix_range];
600 if ([o_mrl characterAtIndex:0] == '/')
601 [[NSWorkspace sharedWorkspace] selectFile: o_mrl inFileViewerRootedAtPath: o_mrl];
608 /* When called retrieves the selected outlineview row and plays that node or item */
609 - (IBAction)preparseItem:(id)sender
612 NSIndexSet *o_selected_indexes;
613 intf_thread_t * p_intf = VLCIntf;
614 playlist_t * p_playlist = pl_Get(p_intf);
615 playlist_item_t *p_item = NULL;
617 o_selected_indexes = [o_outline_view selectedRowIndexes];
618 i_count = [o_selected_indexes count];
620 NSUInteger indexes[i_count];
621 [o_selected_indexes getIndexes:indexes maxCount:i_count inIndexRange:nil];
622 for (int i = 0; i < i_count; i++) {
623 PLItem *o_item = [o_outline_view itemAtRow:indexes[i]];
624 [o_outline_view deselectRow: indexes[i]];
626 if (![o_item isLeaf]) {
627 msg_Dbg(p_intf, "preparsing nodes not implemented");
631 libvlc_MetaRequest(p_intf->p_libvlc, [o_item input], META_REQUEST_OPTION_NONE);
634 [self playlistUpdated];
637 - (IBAction)downloadCoverArt:(id)sender
640 NSIndexSet *o_selected_indexes;
641 intf_thread_t * p_intf = VLCIntf;
642 playlist_t * p_playlist = pl_Get(p_intf);
643 playlist_item_t *p_item = NULL;
645 o_selected_indexes = [o_outline_view selectedRowIndexes];
646 i_count = [o_selected_indexes count];
648 NSUInteger indexes[i_count];
649 [o_selected_indexes getIndexes:indexes maxCount:i_count inIndexRange:nil];
650 for (int i = 0; i < i_count; i++) {
651 PLItem *o_item = [o_outline_view itemAtRow: indexes[i]];
653 if (![o_item isLeaf])
656 libvlc_ArtRequest(p_intf->p_libvlc, [o_item input], META_REQUEST_OPTION_NONE);
658 [self playlistUpdated];
661 - (IBAction)selectAll:(id)sender
663 [o_outline_view selectAll: nil];
666 - (IBAction)showInfoPanel:(id)sender
668 [[[VLCMain sharedInstance] info] initPanel];
671 - (IBAction)deleteItem:(id)sender
674 NSIndexSet *o_selected_indexes;
675 intf_thread_t * p_intf = VLCIntf;
676 playlist_t * p_playlist = pl_Get(p_intf);
678 // check if deletion is allowed
679 if (![[self model] editAllowed])
682 o_selected_indexes = [o_outline_view selectedRowIndexes];
683 i_count = [o_selected_indexes count];
684 retainedRowSelection = [o_selected_indexes firstIndex];
685 if (retainedRowSelection == NSNotFound)
686 retainedRowSelection = 0;
689 NSUInteger indexes[i_count];
690 // if (i_count == [o_outline_view numberOfRows]) {
692 // playlist_NodeDelete(p_playlist, [self currentPlaylistRoot], true, false);
694 // [self playlistUpdated];
697 [o_selected_indexes getIndexes:indexes maxCount:i_count inIndexRange:nil];
698 for (int i = 0; i < i_count; i++) {
699 PLItem *o_item = [o_outline_view itemAtRow: indexes[i]];
700 [o_outline_view deselectRow: indexes[i]];
703 // if (p_item->i_children != -1) {
704 // //is a node and not an item
705 // if (playlist_Status(p_playlist) != PLAYLIST_STOPPED &&
706 // [self isItem: playlist_CurrentPlayingItem(p_playlist) inNode: ((playlist_item_t *)[o_item pointerValue])
707 // checkItemExistence: NO locked:YES] == YES)
708 // // if current item is in selected node and is playing then stop playlist
709 // playlist_Control(p_playlist, PLAYLIST_STOP, pl_Locked);
711 // playlist_NodeDelete(p_playlist, p_item, true, false);
714 playlist_DeleteFromInput(p_playlist, [o_item input], pl_Unlocked);
715 // [[o_item parent] deleteChild:o_item];
717 // [o_outline_view reloadData];
720 // [self playlistUpdated];
723 - (IBAction)sortNodeByName:(id)sender
725 [self sortNode: SORT_TITLE];
728 - (IBAction)sortNodeByAuthor:(id)sender
730 [self sortNode: SORT_ARTIST];
733 - (void)sortNode:(int)i_mode
735 playlist_t * p_playlist = pl_Get(VLCIntf);
736 playlist_item_t * p_item;
738 // TODO why do we need this kind of sort? It looks crap and confusing...
740 // if ([o_outline_view selectedRow] > -1) {
741 // p_item = [[o_outline_view itemAtRow: [o_outline_view selectedRow]] pointerValue];
745 // p_item = [self currentPlaylistRoot]; // If no item is selected, sort the whole playlist
748 // if (p_item->i_children > -1) // the item is a node
749 // playlist_RecursiveNodeSort(p_playlist, p_item, i_mode, ORDER_NORMAL);
751 // playlist_RecursiveNodeSort(p_playlist, p_item->p_parent, i_mode, ORDER_NORMAL);
754 // [self playlistUpdated];
757 - (input_item_t *)createItem:(NSDictionary *)o_one_item
759 intf_thread_t * p_intf = VLCIntf;
760 playlist_t * p_playlist = pl_Get(p_intf);
762 input_item_t *p_input;
763 BOOL b_rem = FALSE, b_dir = FALSE, b_writable = FALSE;
764 NSString *o_uri, *o_name, *o_path;
770 o_uri = (NSString *)[o_one_item objectForKey: @"ITEM_URL"];
771 o_nsurl = [NSURL URLWithString: o_uri];
772 o_path = [o_nsurl path];
773 o_name = (NSString *)[o_one_item objectForKey: @"ITEM_NAME"];
774 o_options = (NSArray *)[o_one_item objectForKey: @"ITEM_OPTIONS"];
776 if ([[NSFileManager defaultManager] fileExistsAtPath:o_path isDirectory:&b_dir] && b_dir &&
777 [[NSWorkspace sharedWorkspace] getFileSystemInfoForPath:o_path isRemovable: &b_rem
778 isWritable:&b_writable isUnmountable:NULL description:NULL type:NULL] && b_rem && !b_writable && [o_nsurl isFileURL]) {
780 NSString *diskType = [VLCOpen getVolumeTypeFromMountPath: o_path];
781 msg_Dbg(p_intf, "detected optical media of type %s in the file input", [diskType UTF8String]);
783 if ([diskType isEqualToString: kVLCMediaDVD])
784 o_uri = [NSString stringWithFormat: @"dvdnav://%@", [VLCOpen getBSDNodeFromMountPath: o_path]];
785 else if ([diskType isEqualToString: kVLCMediaVideoTSFolder])
786 o_uri = [NSString stringWithFormat: @"dvdnav://%@", o_path];
787 else if ([diskType isEqualToString: kVLCMediaAudioCD])
788 o_uri = [NSString stringWithFormat: @"cdda://%@", [VLCOpen getBSDNodeFromMountPath: o_path]];
789 else if ([diskType isEqualToString: kVLCMediaVCD])
790 o_uri = [NSString stringWithFormat: @"vcd://%@#0:0", [VLCOpen getBSDNodeFromMountPath: o_path]];
791 else if ([diskType isEqualToString: kVLCMediaSVCD])
792 o_uri = [NSString stringWithFormat: @"vcd://%@@0:0", [VLCOpen getBSDNodeFromMountPath: o_path]];
793 else if ([diskType isEqualToString: kVLCMediaBD] || [diskType isEqualToString: kVLCMediaBDMVFolder])
794 o_uri = [NSString stringWithFormat: @"bluray://%@", o_path];
796 msg_Warn(VLCIntf, "unknown disk type, treating %s as regular input", [o_path UTF8String]);
798 p_input = input_item_New([o_uri UTF8String], [[[NSFileManager defaultManager] displayNameAtPath: o_path] UTF8String]);
801 p_input = input_item_New([o_uri fileSystemRepresentation], o_name ? [o_name UTF8String] : NULL);
807 NSUInteger count = [o_options count];
808 for (NSUInteger i = 0; i < count; i++)
809 input_item_AddOption(p_input, [[o_options objectAtIndex:i] UTF8String], VLC_INPUT_OPTION_TRUSTED);
812 /* Recent documents menu */
813 if (o_nsurl != nil && (BOOL)config_GetInt(p_playlist, "macosx-recentitems") == YES)
814 [[NSDocumentController sharedDocumentController] noteNewRecentDocumentURL: o_nsurl];
819 - (void)appendArray:(NSArray*)o_array atPos:(int)i_position enqueue:(BOOL)b_enqueue
821 playlist_t * p_playlist = pl_Get(VLCIntf);
822 NSUInteger count = [o_array count];
823 BOOL b_usingPlaylist = [[self model] currentRootType] == ROOT_TYPE_PLAYLIST;
826 for (NSUInteger i_item = 0; i_item < count; i_item++) {
827 input_item_t *p_input;
828 NSDictionary *o_one_item;
831 o_one_item = [o_array objectAtIndex:i_item];
832 p_input = [self createItem: o_one_item];
837 int returnValue = playlist_AddInput(p_playlist, p_input, PLAYLIST_INSERT, i_position == -1 ? PLAYLIST_END : i_position + i_item, b_usingPlaylist, pl_Locked);
838 if (returnValue != VLC_SUCCESS) {
839 vlc_gc_decref(p_input);
843 if (i_item == 0 && !b_enqueue) {
844 playlist_item_t *p_item = playlist_ItemGetByInput(p_playlist, p_input);
845 playlist_Control(p_playlist, PLAYLIST_VIEWPLAY, pl_Locked, p_item->p_parent, p_item);
848 vlc_gc_decref(p_input);
851 [self playlistUpdated];
854 - (void)appendNodeArray:(NSArray*)o_array inNode:(playlist_item_t *)p_node atPos:(int)i_position enqueue:(BOOL)b_enqueue
856 playlist_t * p_playlist = pl_Get(VLCIntf);
857 NSUInteger count = [o_array count];
859 for (NSUInteger i_item = 0; i_item < count; i_item++) {
860 input_item_t *p_input;
861 NSDictionary *o_one_item;
865 o_one_item = [o_array objectAtIndex:i_item];
866 p_input = [self createItem: o_one_item];
872 playlist_NodeAddInput(p_playlist, p_input, p_node,
875 PLAYLIST_END : i_position + i_item,
879 if (i_item == 0 && !b_enqueue) {
880 playlist_item_t *p_item;
881 p_item = playlist_ItemGetByInput(p_playlist, p_input);
882 playlist_Control(p_playlist, PLAYLIST_VIEWPLAY, pl_Locked, p_node, p_item);
885 vlc_gc_decref(p_input);
887 // [self playlistUpdated];
890 - (IBAction)searchItem:(id)sender
892 [[self model] searchUpdate:[o_search_field stringValue]];
895 - (IBAction)recursiveExpandNode:(id)sender
897 NSIndexSet * selectedRows = [o_outline_view selectedRowIndexes];
898 NSUInteger count = [selectedRows count];
899 NSUInteger indexes[count];
900 [selectedRows getIndexes:indexes maxCount:count inIndexRange:nil];
903 playlist_item_t *p_item;
904 for (NSUInteger i = 0; i < count; i++) {
905 o_item = [o_outline_view itemAtRow: indexes[i]];
906 p_item = (playlist_item_t *)[o_item pointerValue];
908 if (![[o_outline_view dataSource] outlineView: o_outline_view isItemExpandable: o_item])
909 o_item = [o_outline_dict objectForKey: [NSString stringWithFormat: @"%p", p_item->p_parent]];
911 /* We need to collapse the node first, since OSX refuses to recursively
912 expand an already expanded node, even if children nodes are collapsed. */
913 [o_outline_view collapseItem: o_item collapseChildren: YES];
914 [o_outline_view expandItem: o_item expandChildren: YES];
916 selectedRows = [o_outline_view selectedRowIndexes];
917 [selectedRows getIndexes:indexes maxCount:count inIndexRange:nil];
921 - (NSMenu *)menuForEvent:(NSEvent *)o_event
923 if (!b_playlistmenu_nib_loaded)
924 b_playlistmenu_nib_loaded = [NSBundle loadNibNamed:@"PlaylistMenu" owner:self];
930 pt = [o_outline_view convertPoint: [o_event locationInWindow] fromView: nil];
931 int row = [o_outline_view rowAtPoint:pt];
932 if (row != -1 && ![[o_outline_view selectedRowIndexes] containsIndex: row])
933 [o_outline_view selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO];
935 b_item_sel = (row != -1 && [o_outline_view selectedRow] != -1);
936 b_rows = [o_outline_view numberOfRows] != 0;
938 playlist_t *p_playlist = pl_Get(VLCIntf);
939 bool b_del_allowed = [[self model] editAllowed];
941 [o_mi_play setEnabled: b_item_sel];
942 [o_mi_delete setEnabled: b_item_sel && b_del_allowed];
943 [o_mi_selectall setEnabled: b_rows];
944 [o_mi_info setEnabled: b_item_sel];
945 [o_mi_preparse setEnabled: b_item_sel];
946 [o_mi_recursive_expand setEnabled: b_item_sel];
947 [o_mi_sort_name setEnabled: b_item_sel];
948 [o_mi_sort_author setEnabled: b_item_sel];
953 - (void)outlineView: (NSOutlineView *)o_tv didClickTableColumn:(NSTableColumn *)o_tc
955 int i_mode, i_type = 0;
956 intf_thread_t *p_intf = VLCIntf;
957 NSString * o_identifier = [o_tc identifier];
959 playlist_t *p_playlist = pl_Get(p_intf);
961 if (o_tc_sortColumn == o_tc)
962 b_isSortDescending = !b_isSortDescending;
964 b_isSortDescending = false;
966 if (b_isSortDescending)
967 i_type = ORDER_REVERSE;
969 i_type = ORDER_NORMAL;
971 [[self model] sortForColumn:o_identifier withMode:i_type];
973 // TODO rework, why do we need a full call here?
974 // [self playlistUpdated];
976 /* Clear indications of any existing column sorting */
977 NSUInteger count = [[o_outline_view tableColumns] count];
978 for (NSUInteger i = 0 ; i < count ; i++)
979 [o_outline_view setIndicatorImage:nil inTableColumn: [[o_outline_view tableColumns] objectAtIndex:i]];
981 [o_outline_view setHighlightedTableColumn:nil];
982 o_tc_sortColumn = nil;
985 o_tc_sortColumn = o_tc;
986 [o_outline_view setHighlightedTableColumn:o_tc];
988 if (b_isSortDescending)
989 [o_outline_view setIndicatorImage:o_descendingSortingImage inTableColumn:o_tc];
991 [o_outline_view setIndicatorImage:o_ascendingSortingImage inTableColumn:o_tc];
995 - (void)outlineView:(NSOutlineView *)outlineView
996 willDisplayCell:(id)cell
997 forTableColumn:(NSTableColumn *)tableColumn
1000 /* this method can be called when VLC is already dead, hence the extra checks */
1001 intf_thread_t * p_intf = VLCIntf;
1004 playlist_t *p_playlist = pl_Get(p_intf);
1009 o_playing_item = [o_outline_dict objectForKey: [NSString stringWithFormat:@"%p", playlist_CurrentPlayingItem(p_playlist)]];
1013 if (config_GetInt(VLCIntf, "macosx-large-text"))
1014 fontToUse = [NSFont systemFontOfSize:13.];
1016 fontToUse = [NSFont systemFontOfSize:11.];
1018 BOOL b_is_playing = NO;
1020 playlist_item_t *p_current_item = playlist_CurrentPlayingItem(p_playlist);
1021 if (p_current_item) {
1022 b_is_playing = p_current_item->i_id == [item plItemId];
1027 TODO: repaint all items bold:
1028 [self isItem: [o_playing_item pointerValue] inNode: [item pointerValue] checkItemExistence:YES locked:NO]
1029 || [o_playing_item isEqual: item]
1033 [cell setFont: [[NSFontManager sharedFontManager] convertFont:fontToUse toHaveTrait:NSBoldFontMask]];
1035 [cell setFont: [[NSFontManager sharedFontManager] convertFont:fontToUse toNotHaveTrait:NSBoldFontMask]];
1040 playlist_t *p_playlist = pl_Get(VLCIntf);
1045 o_playing_item = [o_outline_dict objectForKey: [NSString stringWithFormat:@"%p", playlist_CurrentPlayingItem(p_playlist)]];
1048 return o_playing_item;
1051 - (NSArray *)draggedItems
1053 return [[o_nodes_array arrayByAddingObjectsFromArray: o_items_array] retain];
1056 - (void)setColumn: (NSString *)o_column state: (NSInteger)i_state translationDict:(NSDictionary *)o_dict
1058 NSTableColumn * o_work_tc;
1060 if (i_state == NSOnState) {
1061 NSString *o_title = [o_dict objectForKey:o_column];
1065 o_work_tc = [[NSTableColumn alloc] initWithIdentifier: o_column];
1066 [o_work_tc setEditable: NO];
1067 [[o_work_tc dataCell] setFont: [NSFont controlContentFontOfSize:11.]];
1069 [[o_work_tc headerCell] setStringValue: [o_dict objectForKey:o_column]];
1071 if ([o_column isEqualToString: TRACKNUM_COLUMN]) {
1072 [o_work_tc setWidth: 20.];
1073 [o_work_tc setResizingMask: NSTableColumnNoResizing];
1074 [[o_work_tc headerCell] setStringValue: @"#"];
1077 [o_outline_view addTableColumn: o_work_tc];
1078 [o_work_tc release];
1079 [o_outline_view reloadData];
1080 [o_outline_view setNeedsDisplay: YES];
1083 [o_outline_view removeTableColumn: [o_outline_view tableColumnWithIdentifier: o_column]];
1085 [o_outline_view setOutlineTableColumn: [o_outline_view tableColumnWithIdentifier:TITLE_COLUMN]];
1088 - (void)saveTableColumns
1090 NSMutableArray * o_arrayToSave = [[NSMutableArray alloc] init];
1091 NSArray * o_columns = [[NSArray alloc] initWithArray:[o_outline_view tableColumns]];
1092 NSUInteger count = [o_columns count];
1093 NSTableColumn * o_currentColumn;
1094 for (NSUInteger i = 0; i < count; i++) {
1095 o_currentColumn = [o_columns objectAtIndex:i];
1096 [o_arrayToSave addObject:[NSArray arrayWithObjects:[o_currentColumn identifier], [NSNumber numberWithFloat:[o_currentColumn width]], nil]];
1098 [[NSUserDefaults standardUserDefaults] setObject: o_arrayToSave forKey:@"PlaylistColumnSelection"];
1099 [[NSUserDefaults standardUserDefaults] synchronize];
1100 [o_columns release];
1101 [o_arrayToSave release];
1104 - (BOOL)isValidResumeItem:(input_item_t *)p_item
1106 char *psz_url = input_item_GetURI(p_item);
1107 NSString *o_url_string = toNSStr(psz_url);
1110 if ([o_url_string isEqualToString:@""])
1113 NSURL *o_url = [NSURL URLWithString:o_url_string];
1115 if (![o_url isFileURL])
1119 if (![[NSFileManager defaultManager] fileExistsAtPath:[o_url path] isDirectory:&isDir])
1128 - (void)updateAlertWindow:(NSTimer *)timer
1130 NSAlert *alert = [timer userInfo];
1132 --currentResumeTimeout;
1133 if (currentResumeTimeout <= 0) {
1134 [[alert window] close];
1138 NSString *buttonLabel = _NS("Restart playback");
1139 buttonLabel = [buttonLabel stringByAppendingFormat:@" (%d)", currentResumeTimeout];
1141 [[[alert buttons] objectAtIndex:2] setTitle:buttonLabel];
1144 - (void)continuePlaybackWhereYouLeftOff:(input_thread_t *)p_input_thread
1146 NSDictionary *recentlyPlayedFiles = [[NSUserDefaults standardUserDefaults] objectForKey:@"recentlyPlayedMedia"];
1147 if (!recentlyPlayedFiles)
1150 input_item_t *p_item = input_GetItem(p_input_thread);
1154 /* allow the user to over-write the start/stop/run-time */
1155 if (var_GetFloat(p_input_thread, "run-time") > 0 ||
1156 var_GetFloat(p_input_thread, "start-time") > 0 ||
1157 var_GetFloat(p_input_thread, "stop-time") > 0) {
1161 /* check for file existance before resuming */
1162 if (![self isValidResumeItem:p_item])
1165 char *psz_url = decode_URI(input_item_GetURI(p_item));
1168 NSString *url = toNSStr(psz_url);
1171 NSNumber *lastPosition = [recentlyPlayedFiles objectForKey:url];
1172 if (!lastPosition || lastPosition.intValue <= 0)
1175 int settingValue = config_GetInt(VLCIntf, "macosx-continue-playback");
1176 if (settingValue == 2) // never resume
1179 NSInteger returnValue = NSAlertErrorReturn;
1180 if (settingValue == 0) { // ask
1182 currentResumeTimeout = 6;
1183 NSString *o_restartButtonLabel = _NS("Restart playback");
1184 o_restartButtonLabel = [o_restartButtonLabel stringByAppendingFormat:@" (%d)", currentResumeTimeout];
1185 NSAlert *theAlert = [NSAlert alertWithMessageText:_NS("Continue playback?") defaultButton:_NS("Continue") alternateButton:o_restartButtonLabel otherButton:_NS("Always continue") informativeTextWithFormat:_NS("Playback of \"%@\" will continue at %@"), [NSString stringWithUTF8String:input_item_GetTitleFbName(p_item)], [[VLCStringUtility sharedInstance] stringForTime:lastPosition.intValue]];
1187 NSTimer *timer = [NSTimer timerWithTimeInterval:1
1189 selector:@selector(updateAlertWindow:)
1193 [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSModalPanelRunLoopMode];
1195 [[VLCCoreInteraction sharedInstance] pause];
1196 returnValue = [theAlert runModal];
1198 [[VLCCoreInteraction sharedInstance] playOrPause];
1200 // restart button was pressed or timeout happened
1201 if (returnValue == NSAlertAlternateReturn ||
1202 returnValue == NSRunAbortedResponse)
1206 mtime_t lastPos = (mtime_t)lastPosition.intValue * 1000000;
1207 msg_Dbg(VLCIntf, "continuing playback at %lld", lastPos);
1208 var_SetTime(p_input_thread, "time", lastPos);
1210 if (returnValue == NSAlertOtherReturn)
1211 config_PutInt(VLCIntf, "macosx-continue-playback", 1);
1214 - (void)storePlaybackPositionForItem:(input_thread_t *)p_input_thread
1216 if (!var_InheritBool(VLCIntf, "macosx-recentitems"))
1219 input_item_t *p_item = input_GetItem(p_input_thread);
1223 if (![self isValidResumeItem:p_item])
1226 char *psz_url = decode_URI(input_item_GetURI(p_item));
1229 NSString *url = toNSStr(psz_url);
1232 NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
1233 NSMutableDictionary *mutDict = [[NSMutableDictionary alloc] initWithDictionary:[defaults objectForKey:@"recentlyPlayedMedia"]];
1235 float relativePos = var_GetFloat(p_input_thread, "position");
1236 mtime_t pos = var_GetTime(p_input_thread, "time") / 1000000;
1237 mtime_t dur = input_item_GetDuration(p_item) / 1000000;
1239 NSMutableArray *mediaList = [[defaults objectForKey:@"recentlyPlayedMediaList"] mutableCopy];
1241 if (relativePos > .05 && relativePos < .95 && dur > 180) {
1242 [mutDict setObject:[NSNumber numberWithInt:pos] forKey:url];
1244 [mediaList removeObject:url];
1245 [mediaList addObject:url];
1246 NSUInteger mediaListCount = mediaList.count;
1247 if (mediaListCount > 30) {
1248 for (NSUInteger x = 0; x < mediaListCount - 30; x++) {
1249 [mutDict removeObjectForKey:[mediaList objectAtIndex:0]];
1250 [mediaList removeObjectAtIndex:0];
1254 [mutDict removeObjectForKey:url];
1255 [mediaList removeObject:url];
1257 [defaults setObject:mutDict forKey:@"recentlyPlayedMedia"];
1258 [defaults setObject:mediaList forKey:@"recentlyPlayedMediaList"];
1259 [defaults synchronize];
1262 [mediaList release];
1268 @implementation VLCPlaylist (NSOutlineViewDataSource)
1269 /* return the number of children for Obj-C pointer item */ /* DONE */
1270 - (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
1273 playlist_item_t *p_item = NULL;
1274 playlist_t * p_playlist = pl_Get(VLCIntf);
1275 //assert(outlineView == o_outline_view);
1279 p_item = p_current_root_item;
1281 p_item = (playlist_item_t *)[item pointerValue];
1284 i_return = p_item->i_children;
1287 return i_return > 0 ? i_return : 0;
1290 /* return the child at index for the Obj-C pointer item */ /* DONE */
1291 - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item
1293 playlist_item_t *p_return = NULL, *p_item = NULL;
1295 playlist_t * p_playlist = pl_Get(VLCIntf);
1299 p_item = p_current_root_item; /* root object */
1301 p_item = (playlist_item_t *)[item pointerValue];
1303 if (p_item && index < p_item->i_children && index >= 0)
1304 p_return = p_item->pp_children[index];
1307 o_value = [o_outline_dict objectForKey:[NSString stringWithFormat: @"%p", p_return]];
1309 if (o_value == nil) {
1310 /* FIXME: Why is there a warning if that happens all the time and seems
1311 * to be normal? Add an assert and fix it.
1312 * msg_Warn(VLCIntf, "playlist item misses pointer value, adding one"); */
1313 o_value = [[NSValue valueWithPointer: p_return] retain];
1316 [o_outline_dict setObject:o_value forKey:[NSString stringWithFormat:@"%p", [o_value pointerValue]]];
1321 /* is the item expandable */
1322 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
1325 playlist_t *p_playlist = pl_Get(VLCIntf);
1330 if (p_current_root_item) {
1331 i_return = p_current_root_item->i_children;
1334 playlist_item_t *p_item = (playlist_item_t *)[item pointerValue];
1336 i_return = p_item->i_children;
1340 return (i_return >= 0);
1343 /* retrieve the string values for the cells */
1344 - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)o_tc byItem:(id)item
1348 playlist_item_t *p_item;
1350 /* For error handling */
1351 static BOOL attempted_reload = NO;
1353 if (item == nil || ![item isKindOfClass: [NSValue class]]) {
1354 /* Attempt to fix the error by asking for a data redisplay
1355 * This might cause infinite loop, so add a small check */
1356 if (!attempted_reload) {
1357 attempted_reload = YES;
1358 [outlineView reloadData];
1363 p_item = (playlist_item_t *)[item pointerValue];
1364 if (!p_item || !p_item->p_input) {
1365 /* Attempt to fix the error by asking for a data redisplay
1366 * This might cause infinite loop, so add a small check */
1367 if (!attempted_reload) {
1368 attempted_reload = YES;
1369 [outlineView reloadData];
1374 attempted_reload = NO;
1375 NSString * o_identifier = [o_tc identifier];
1377 if ([o_identifier isEqualToString:TRACKNUM_COLUMN]) {
1378 psz_value = input_item_GetTrackNumber(p_item->p_input);
1380 o_value = [NSString stringWithUTF8String:psz_value];
1383 } else if ([o_identifier isEqualToString:TITLE_COLUMN]) {
1384 /* sanity check to prevent the NSString class from crashing */
1385 char *psz_title = input_item_GetTitleFbName(p_item->p_input);
1387 o_value = [NSString stringWithUTF8String:psz_title];
1390 } else if ([o_identifier isEqualToString:ARTIST_COLUMN]) {
1391 psz_value = input_item_GetArtist(p_item->p_input);
1393 o_value = [NSString stringWithUTF8String:psz_value];
1396 } else if ([o_identifier isEqualToString:@"duration"]) {
1397 char psz_duration[MSTRTIME_MAX_SIZE];
1398 mtime_t dur = input_item_GetDuration(p_item->p_input);
1400 secstotimestr(psz_duration, dur/1000000);
1401 o_value = [NSString stringWithUTF8String:psz_duration];
1405 } else if ([o_identifier isEqualToString:GENRE_COLUMN]) {
1406 psz_value = input_item_GetGenre(p_item->p_input);
1408 o_value = [NSString stringWithUTF8String:psz_value];
1411 } else if ([o_identifier isEqualToString:ALBUM_COLUMN]) {
1412 psz_value = input_item_GetAlbum(p_item->p_input);
1414 o_value = [NSString stringWithUTF8String:psz_value];
1417 } else if ([o_identifier isEqualToString:DESCRIPTION_COLUMN]) {
1418 psz_value = input_item_GetDescription(p_item->p_input);
1420 o_value = [NSString stringWithUTF8String:psz_value];
1423 } else if ([o_identifier isEqualToString:DATE_COLUMN]) {
1424 psz_value = input_item_GetDate(p_item->p_input);
1426 o_value = [NSString stringWithUTF8String:psz_value];
1429 } else if ([o_identifier isEqualToString:LANGUAGE_COLUMN]) {
1430 psz_value = input_item_GetLanguage(p_item->p_input);
1432 o_value = [NSString stringWithUTF8String:psz_value];
1436 else if ([o_identifier isEqualToString:URI_COLUMN]) {
1437 psz_value = decode_URI(input_item_GetURI(p_item->p_input));
1439 o_value = [NSString stringWithUTF8String:psz_value];
1443 else if ([o_identifier isEqualToString:FILESIZE_COLUMN]) {
1444 psz_value = input_item_GetURI(p_item->p_input);
1447 NSURL *url = [NSURL URLWithString:[NSString stringWithUTF8String:psz_value]];
1448 if ([url isFileURL]) {
1449 NSFileManager *fileManager = [NSFileManager defaultManager];
1450 if ([fileManager fileExistsAtPath:[url path]]) {
1452 NSDictionary *attributes = [fileManager attributesOfItemAtPath:[url path] error:&error];
1453 o_value = [VLCByteCountFormatter stringFromByteCount:[attributes fileSize] countStyle:NSByteCountFormatterCountStyleDecimal];
1459 else if ([o_identifier isEqualToString:@"status"]) {
1460 if (input_item_HasErrorWhenReading(p_item->p_input)) {
1461 o_value = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kAlertCautionIcon)];
1462 [o_value setSize: NSMakeSize(16,16)];
1470 /* Required for drag & drop and reordering */
1471 - (BOOL)outlineView:(NSOutlineView *)outlineView writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pboard
1473 playlist_t *p_playlist = pl_Get(VLCIntf);
1475 /* First remove the items that were moved during the last drag & drop
1477 [o_items_array removeAllObjects];
1478 [o_nodes_array removeAllObjects];
1480 NSUInteger itemCount = [items count];
1482 for (NSUInteger i = 0 ; i < itemCount ; i++) {
1483 id o_item = [items objectAtIndex:i];
1485 /* Fill the items and nodes to move in 2 different arrays */
1486 if (![o_item isLeaf])
1487 [o_nodes_array addObject: o_item];
1489 [o_items_array addObject: o_item];
1492 /* Now we need to check if there are selected items that are in already
1493 selected nodes. In that case, we only want to move the nodes */
1494 [self removeItemsFrom: o_nodes_array ifChildrenOf: o_nodes_array];
1495 [self removeItemsFrom: o_items_array ifChildrenOf: o_nodes_array];
1497 /* We add the "VLCPlaylistItemPboardType" type to be able to recognize
1498 a Drop operation coming from the playlist. */
1500 [pboard declareTypes: [NSArray arrayWithObject:@"VLCPlaylistItemPboardType"] owner: self];
1501 [pboard setData:[NSData data] forType:@"VLCPlaylistItemPboardType"];
1506 - (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id <NSDraggingInfo>)info proposedItem:(id)item proposedChildIndex:(NSInteger)index
1508 playlist_t *p_playlist = pl_Get(VLCIntf);
1509 NSPasteboard *o_pasteboard = [info draggingPasteboard];
1511 if (!p_playlist) return NSDragOperationNone;
1513 /* Dropping ON items is not allowed if item is not a node */
1515 if (index == NSOutlineViewDropOnItemIndex &&
1516 ((playlist_item_t *)[item pointerValue])->i_children == -1) {
1517 return NSDragOperationNone;
1521 /* We refuse to drop an item in anything else than a child of the General
1522 Node. We still accept items that would be root nodes of the outlineview
1523 however, to allow drop in an empty playlist. */
1525 // if (!(([self isItem: [item pointerValue] inNode: p_playlist->p_local_category checkItemExistence: NO locked: NO] ||
1526 // (var_CreateGetBool(p_playlist, "media-library") && [self isItem: [item pointerValue] inNode: p_playlist->p_ml_category checkItemExistence: NO locked: NO])) || item == nil)) {
1527 // return NSDragOperationNone;
1530 /* Drop from the Playlist */
1531 if ([[o_pasteboard types] containsObject: @"VLCPlaylistItemPboardType"]) {
1532 NSUInteger count = [o_nodes_array count];
1533 for (NSUInteger i = 0 ; i < count ; i++) {
1534 /* We refuse to Drop in a child of an item we are moving */
1535 if ([self isItem: item inNode: [o_nodes_array objectAtIndex:i] checkItemExistence: NO locked:NO]) {
1536 return NSDragOperationNone;
1539 return NSDragOperationMove;
1541 /* Drop from the Finder */
1542 else if ([[o_pasteboard types] containsObject: NSFilenamesPboardType]) {
1543 return NSDragOperationGeneric;
1545 return NSDragOperationNone;
1548 - (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id <NSDraggingInfo>)info item:(id)item childIndex:(NSInteger)index
1550 playlist_t * p_playlist = pl_Get(VLCIntf);
1551 NSPasteboard *o_pasteboard = [info draggingPasteboard];
1553 /* Drag & Drop inside the playlist */
1554 if ([[o_pasteboard types] containsObject: @"VLCPlaylistItemPboardType"]) {
1555 if (index == -1) // this is no valid target, sanitize to top of table
1559 playlist_item_t *p_new_parent, *p_item = NULL;
1560 NSArray *o_all_items = [o_nodes_array arrayByAddingObjectsFromArray: o_items_array];
1561 /* If the item is to be dropped as root item of the outline, make it a
1562 child of the respective general node, if is either the pl or the ml
1563 Else, choose the proposed parent as parent. */
1565 // TODO edit allowed / no drop in other types
1566 if ([[self model] currentRootType] == ROOT_TYPE_PLAYLIST ||
1567 [[self model] currentRootType] == ROOT_TYPE_MEDIALIBRARY)
1568 item = [[self model] rootItem];
1573 /* Make sure the proposed parent is a node.
1574 (This should never be true) */
1575 if (p_new_parent->i_children < 0)
1578 NSUInteger count = [o_all_items count];
1582 playlist_item_t **pp_items = (playlist_item_t **)calloc(count, sizeof(playlist_item_t*));
1587 p_new_parent = playlist_ItemGetById(p_playlist, [item plItemId]);
1588 if (!p_new_parent) {
1594 for (NSUInteger i = 0; i < count; i++) {
1595 p_item = playlist_ItemGetById(p_playlist, [[o_all_items objectAtIndex:i] plItemId]);
1597 pp_items[j++] = p_item;
1600 if (j == 0 || playlist_TreeMoveMany(p_playlist, j, pp_items, p_new_parent, index) != VLC_SUCCESS) {
1609 [self playlistUpdated];
1610 i_row = [o_outline_view rowForItem:[o_all_items objectAtIndex:0]];
1613 i_row = [o_outline_view rowForItem:item];
1615 [o_outline_view deselectAll: self];
1616 [o_outline_view selectRowIndexes:[NSIndexSet indexSetWithIndex:i_row] byExtendingSelection:NO];
1617 [o_outline_view scrollRowToVisible: i_row];
1622 else if ([[o_pasteboard types] containsObject: NSFilenamesPboardType]) {
1623 // TODO: can this already checked in drop validation?
1624 if (![[self model] editAllowed])
1627 playlist_item_t *p_node = [item pointerValue];
1629 NSArray *o_values = [[o_pasteboard propertyListForType: NSFilenamesPboardType]
1630 sortedArrayUsingSelector: @selector(caseInsensitiveCompare:)];
1631 NSUInteger count = [o_values count];
1632 NSMutableArray *o_array = [NSMutableArray arrayWithCapacity:count];
1633 input_thread_t * p_input = pl_CurrentInput(VLCIntf);
1635 if (count == 1 && p_input) {
1636 int i_result = input_AddSubtitleOSD(p_input, vlc_path2uri([[o_values objectAtIndex:0] UTF8String], NULL), true, true);
1637 vlc_object_release(p_input);
1638 if (i_result == VLC_SUCCESS)
1642 vlc_object_release(p_input);
1644 for (NSUInteger i = 0; i < count; i++) {
1645 NSDictionary *o_dic;
1646 char *psz_uri = vlc_path2uri([[o_values objectAtIndex:i] UTF8String], NULL);
1650 o_dic = [NSDictionary dictionaryWithObject:[NSString stringWithCString:psz_uri encoding:NSUTF8StringEncoding] forKey:@"ITEM_URL"];
1654 [o_array addObject: o_dic];
1658 [self appendArray:o_array atPos:index enqueue: YES];
1660 assert(p_node->i_children != -1);
1661 [self appendNodeArray:o_array inNode: p_node atPos:index enqueue:YES];