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"
55 #import <vlc_interface.h>
58 /*****************************************************************************
59 * VLCPlaylistView implementation
60 *****************************************************************************/
61 @implementation VLCPlaylistView
63 - (NSMenu *)menuForEvent:(NSEvent *)o_event
65 return([(VLCPlaylist *)[self delegate] menuForEvent: o_event]);
68 - (void)keyDown:(NSEvent *)o_event
72 if ([[o_event characters] length])
73 key = [[o_event characters] characterAtIndex: 0];
76 case NSDeleteCharacter:
77 case NSDeleteFunctionKey:
78 case NSDeleteCharFunctionKey:
79 case NSBackspaceCharacter:
80 [(VLCPlaylist *)[self delegate] deleteItem:self];
83 case NSEnterCharacter:
84 case NSCarriageReturnCharacter:
85 [(VLCPlaylist *)[[VLCMain sharedInstance] playlist] playItem:nil];
89 [super keyDown: o_event];
94 - (BOOL)validateMenuItem:(NSMenuItem *)item
96 if (([self numberOfSelectedRows] >= 1 && [item action] == @selector(delete:)) || [item action] == @selector(selectAll:))
102 - (BOOL)acceptsFirstResponder
107 - (BOOL)becomeFirstResponder
109 [self setNeedsDisplay:YES];
113 - (BOOL)resignFirstResponder
115 [self setNeedsDisplay:YES];
119 - (IBAction)delete:(id)sender
121 [[[VLCMain sharedInstance] playlist] deleteItem: sender];
126 /*****************************************************************************
127 * VLCPlaylistCommon implementation
129 * This class the superclass of the VLCPlaylist and VLCPlaylistWizard.
130 * It contains the common methods and elements of these 2 entities.
131 *****************************************************************************/
132 @interface VLCPlaylistCommon ()
134 playlist_item_t * p_current_root_item;
138 @implementation VLCPlaylistCommon
142 playlist_t * p_playlist = pl_Get(VLCIntf);
143 p_current_root_item = p_playlist->p_local_category;
147 o_outline_dict = [[NSMutableDictionary alloc] init];
154 playlist_t * p_playlist = pl_Get(VLCIntf);
155 [o_outline_view setTarget: self];
156 [o_outline_view setDelegate: self];
157 [o_outline_view setDataSource: self];
158 [o_outline_view setAllowsEmptySelection: NO];
159 [o_outline_view expandItem: [o_outline_view itemAtRow:0]];
161 [o_outline_view_other setTarget: self];
162 [o_outline_view_other setDelegate: self];
163 [o_outline_view_other setDataSource: self];
164 [o_outline_view_other setAllowsEmptySelection: NO];
166 [[o_tc_name_other headerCell] setStringValue:_NS("Name")];
167 [[o_tc_author_other headerCell] setStringValue:_NS("Author")];
168 [[o_tc_duration_other headerCell] setStringValue:_NS("Duration")];
171 - (void)setPlaylistRoot: (playlist_item_t *)root_item
173 p_current_root_item = root_item;
174 [o_outline_view reloadData];
175 [o_outline_view_other reloadData];
178 - (playlist_item_t *)currentPlaylistRoot
180 return p_current_root_item;
183 - (NSOutlineView *)outlineView
185 return o_outline_view;
188 - (playlist_item_t *)selectedPlaylistItem
190 return [[o_outline_view itemAtRow: [o_outline_view selectedRow]]
195 [o_outline_dict release];
201 @implementation VLCPlaylistCommon (NSOutlineViewDataSource)
202 /* return the number of children for Obj-C pointer item */ /* DONE */
203 - (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
206 playlist_item_t *p_item = NULL;
207 playlist_t * p_playlist = pl_Get(VLCIntf);
208 //assert(outlineView == o_outline_view);
212 p_item = p_current_root_item;
214 p_item = (playlist_item_t *)[item pointerValue];
217 i_return = p_item->i_children;
220 return i_return > 0 ? i_return : 0;
223 /* return the child at index for the Obj-C pointer item */ /* DONE */
224 - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item
226 playlist_item_t *p_return = NULL, *p_item = NULL;
228 playlist_t * p_playlist = pl_Get(VLCIntf);
232 p_item = p_current_root_item; /* root object */
234 p_item = (playlist_item_t *)[item pointerValue];
236 if (p_item && index < p_item->i_children && index >= 0)
237 p_return = p_item->pp_children[index];
240 o_value = [o_outline_dict objectForKey:[NSString stringWithFormat: @"%p", p_return]];
242 if (o_value == nil) {
243 /* FIXME: Why is there a warning if that happens all the time and seems
244 * to be normal? Add an assert and fix it.
245 * msg_Warn(VLCIntf, "playlist item misses pointer value, adding one"); */
246 o_value = [[NSValue valueWithPointer: p_return] retain];
251 /* is the item expandable */
252 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
255 playlist_t *p_playlist = pl_Get(VLCIntf);
260 if (p_current_root_item) {
261 i_return = p_current_root_item->i_children;
264 playlist_item_t *p_item = (playlist_item_t *)[item pointerValue];
266 i_return = p_item->i_children;
270 return (i_return >= 0);
273 /* retrieve the string values for the cells */
274 - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)o_tc byItem:(id)item
278 playlist_item_t *p_item;
280 /* For error handling */
281 static BOOL attempted_reload = NO;
283 if (item == nil || ![item isKindOfClass: [NSValue class]]) {
284 /* Attempt to fix the error by asking for a data redisplay
285 * This might cause infinite loop, so add a small check */
286 if (!attempted_reload) {
287 attempted_reload = YES;
288 [outlineView reloadData];
293 p_item = (playlist_item_t *)[item pointerValue];
294 if (!p_item || !p_item->p_input) {
295 /* Attempt to fix the error by asking for a data redisplay
296 * This might cause infinite loop, so add a small check */
297 if (!attempted_reload) {
298 attempted_reload = YES;
299 [outlineView reloadData];
304 attempted_reload = NO;
305 NSString * o_identifier = [o_tc identifier];
307 if ([o_identifier isEqualToString:TRACKNUM_COLUMN]) {
308 psz_value = input_item_GetTrackNumber(p_item->p_input);
310 o_value = [NSString stringWithUTF8String:psz_value];
313 } else if ([o_identifier isEqualToString:TITLE_COLUMN]) {
314 /* sanity check to prevent the NSString class from crashing */
315 char *psz_title = input_item_GetTitleFbName(p_item->p_input);
317 o_value = [NSString stringWithUTF8String:psz_title];
320 } else if ([o_identifier isEqualToString:ARTIST_COLUMN]) {
321 psz_value = input_item_GetArtist(p_item->p_input);
323 o_value = [NSString stringWithUTF8String:psz_value];
326 } else if ([o_identifier isEqualToString:@"duration"]) {
327 char psz_duration[MSTRTIME_MAX_SIZE];
328 mtime_t dur = input_item_GetDuration(p_item->p_input);
330 secstotimestr(psz_duration, dur/1000000);
331 o_value = [NSString stringWithUTF8String:psz_duration];
335 } else if ([o_identifier isEqualToString:GENRE_COLUMN]) {
336 psz_value = input_item_GetGenre(p_item->p_input);
338 o_value = [NSString stringWithUTF8String:psz_value];
341 } else if ([o_identifier isEqualToString:ALBUM_COLUMN]) {
342 psz_value = input_item_GetAlbum(p_item->p_input);
344 o_value = [NSString stringWithUTF8String:psz_value];
347 } else if ([o_identifier isEqualToString:DESCRIPTION_COLUMN]) {
348 psz_value = input_item_GetDescription(p_item->p_input);
350 o_value = [NSString stringWithUTF8String:psz_value];
353 } else if ([o_identifier isEqualToString:DATE_COLUMN]) {
354 psz_value = input_item_GetDate(p_item->p_input);
356 o_value = [NSString stringWithUTF8String:psz_value];
359 } else if ([o_identifier isEqualToString:LANGUAGE_COLUMN]) {
360 psz_value = input_item_GetLanguage(p_item->p_input);
362 o_value = [NSString stringWithUTF8String:psz_value];
366 else if ([o_identifier isEqualToString:URI_COLUMN]) {
367 psz_value = decode_URI(input_item_GetURI(p_item->p_input));
369 o_value = [NSString stringWithUTF8String:psz_value];
373 else if ([o_identifier isEqualToString:FILESIZE_COLUMN]) {
374 psz_value = input_item_GetURI(p_item->p_input);
377 NSURL *url = [NSURL URLWithString:[NSString stringWithUTF8String:psz_value]];
378 if ([url isFileURL]) {
379 NSFileManager *fileManager = [NSFileManager defaultManager];
380 if ([fileManager fileExistsAtPath:[url path]]) {
382 NSDictionary *attributes = [fileManager attributesOfItemAtPath:[url path] error:&error];
383 o_value = [VLCByteCountFormatter stringFromByteCount:[attributes fileSize] countStyle:NSByteCountFormatterCountStyleDecimal];
389 else if ([o_identifier isEqualToString:@"status"]) {
390 if (input_item_HasErrorWhenReading(p_item->p_input)) {
391 o_value = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kAlertCautionIcon)];
392 [o_value setSize: NSMakeSize(16,16)];
401 /*****************************************************************************
402 * VLCPlaylistWizard implementation
403 *****************************************************************************/
404 @implementation VLCPlaylistWizard
406 - (IBAction)reloadOutlineView
408 /* Only reload the outlineview if the wizard window is open since this can
409 be quite long on big playlists */
410 if ([[o_outline_view window] isVisible])
411 [o_outline_view reloadData];
416 /*****************************************************************************
417 * An extension to NSOutlineView's interface to fix compilation warnings
418 * and let us access these 2 functions properly.
419 * This uses a private API, but works fine on all current OSX releases.
420 * Radar ID 11739459 request a public API for this. However, it is probably
421 * easier and faster to recreate similar looking bitmaps ourselves.
422 *****************************************************************************/
424 @interface NSOutlineView (UndocumentedSortImages)
425 + (NSImage *)_defaultTableHeaderSortImage;
426 + (NSImage *)_defaultTableHeaderReverseSortImage;
430 /*****************************************************************************
431 * VLCPlaylist implementation
432 *****************************************************************************/
433 @interface VLCPlaylist ()
435 NSImage *o_descendingSortingImage;
436 NSImage *o_ascendingSortingImage;
438 NSMutableArray *o_nodes_array;
439 NSMutableArray *o_items_array;
441 BOOL b_selected_item_met;
442 BOOL b_isSortDescending;
444 NSInteger retainedRowSelection;
446 BOOL b_playlistmenu_nib_loaded;
450 - (void)saveTableColumns;
453 @implementation VLCPlaylist
456 NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
457 NSMutableArray * o_columnArray = [[NSMutableArray alloc] init];
458 [o_columnArray addObject: [NSArray arrayWithObjects:TITLE_COLUMN, [NSNumber numberWithFloat:190.], nil]];
459 [o_columnArray addObject: [NSArray arrayWithObjects:ARTIST_COLUMN, [NSNumber numberWithFloat:95.], nil]];
460 [o_columnArray addObject: [NSArray arrayWithObjects:DURATION_COLUMN, [NSNumber numberWithFloat:95.], nil]];
461 NSDictionary *appDefaults = [NSDictionary dictionaryWithObject:[NSArray arrayWithArray:o_columnArray] forKey: @"PlaylistColumnSelection"];
463 [defaults registerDefaults:appDefaults];
464 [o_columnArray release];
471 o_nodes_array = [[NSMutableArray alloc] init];
472 o_items_array = [[NSMutableArray alloc] init];
479 [o_nodes_array release];
480 [o_items_array release];
489 playlist_t * p_playlist = pl_Get(VLCIntf);
491 [super awakeFromNib];
494 [o_outline_view setDoubleAction: @selector(playItem:)];
495 [o_outline_view_other setDoubleAction: @selector(playItem:)];
497 [o_outline_view registerForDraggedTypes: [NSArray arrayWithObjects:NSFilenamesPboardType, @"VLCPlaylistItemPboardType", nil]];
498 [o_outline_view setIntercellSpacing: NSMakeSize (0.0, 1.0)];
500 [o_outline_view_other registerForDraggedTypes: [NSArray arrayWithObjects:NSFilenamesPboardType, @"VLCPlaylistItemPboardType", nil]];
501 [o_outline_view_other setIntercellSpacing: NSMakeSize (0.0, 1.0)];
503 /* This uses a private API, but works fine on all current OSX releases.
504 * Radar ID 11739459 request a public API for this. However, it is probably
505 * easier and faster to recreate similar looking bitmaps ourselves. */
506 o_ascendingSortingImage = [[NSOutlineView class] _defaultTableHeaderSortImage];
507 o_descendingSortingImage = [[NSOutlineView class] _defaultTableHeaderReverseSortImage];
509 o_tc_sortColumn = nil;
511 NSArray * o_columnArray = [[NSUserDefaults standardUserDefaults] arrayForKey:@"PlaylistColumnSelection"];
512 NSUInteger count = [o_columnArray count];
514 id o_menu = [[VLCMain sharedInstance] mainMenu];
517 NSMenu *o_context_menu = [o_menu setupPlaylistTableColumnsMenu];
518 [o_playlist_header setMenu: o_context_menu];
520 for (NSUInteger i = 0; i < count; i++) {
521 o_column = [[o_columnArray objectAtIndex:i] objectAtIndex:0];
522 if ([o_column isEqualToString:@"status"])
525 [o_menu setPlaylistColumnTableState: NSOnState forColumn: o_column];
526 [[o_outline_view tableColumnWithIdentifier: o_column] setWidth: [[[o_columnArray objectAtIndex:i] objectAtIndex:1] floatValue]];
529 [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(applicationWillTerminate:) name: NSApplicationWillTerminateNotification object: nil];
534 - (void)applicationWillTerminate:(NSNotification *)notification
536 /* 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 */
537 [self saveTableColumns];
540 - (void)searchfieldChanged:(NSNotification *)o_notification
542 [o_search_field setStringValue:[[o_notification object] stringValue]];
547 [o_mi_play setTitle: _NS("Play")];
548 [o_mi_delete setTitle: _NS("Delete")];
549 [o_mi_recursive_expand setTitle: _NS("Expand Node")];
550 [o_mi_selectall setTitle: _NS("Select All")];
551 [o_mi_info setTitle: _NS("Media Information...")];
552 [o_mi_dl_cover_art setTitle: _NS("Download Cover Art")];
553 [o_mi_preparse setTitle: _NS("Fetch Meta Data")];
554 [o_mi_revealInFinder setTitle: _NS("Reveal in Finder")];
555 [o_mm_mi_revealInFinder setTitle: _NS("Reveal in Finder")];
556 [[o_mm_mi_revealInFinder menu] setAutoenablesItems: NO];
557 [o_mi_sort_name setTitle: _NS("Sort Node by Name")];
558 [o_mi_sort_author setTitle: _NS("Sort Node by Author")];
560 [o_search_field setToolTip: _NS("Search in Playlist")];
561 [o_search_field_other setToolTip: _NS("Search in Playlist")];
564 - (void)playlistUpdated
566 /* Clear indications of any existing column sorting */
567 NSUInteger count = [[o_outline_view tableColumns] count];
568 for (NSUInteger i = 0 ; i < count ; i++)
569 [o_outline_view setIndicatorImage:nil inTableColumn: [[o_outline_view tableColumns] objectAtIndex:i]];
571 [o_outline_view setHighlightedTableColumn:nil];
572 o_tc_sortColumn = nil;
573 // TODO Find a way to keep the dict size to a minimum
574 //[o_outline_dict removeAllObjects];
575 [o_outline_view reloadData];
576 [[[[VLCMain sharedInstance] wizard] playlistWizard] reloadOutlineView];
577 [[[[VLCMain sharedInstance] bookmarks] dataTable] reloadData];
579 [o_outline_view selectRowIndexes:[NSIndexSet indexSetWithIndex:retainedRowSelection] byExtendingSelection:NO];
581 [self outlineViewSelectionDidChange: nil];
582 [[VLCMain sharedInstance] updateMainWindow];
585 - (void)outlineViewSelectionDidChange:(NSNotification *)notification
588 playlist_item_t * p_item = [[o_outline_view itemAtRow:[o_outline_view selectedRow]] pointerValue];
591 /* update the state of our Reveal-in-Finder menu items */
592 NSMutableString *o_mrl;
593 char *psz_uri = input_item_GetURI(p_item->p_input);
595 [o_mi_revealInFinder setEnabled: NO];
596 [o_mm_mi_revealInFinder setEnabled: NO];
598 o_mrl = [NSMutableString stringWithUTF8String: psz_uri];
600 /* perform some checks whether it is a file and if it is local at all... */
601 NSRange prefix_range = [o_mrl rangeOfString: @"file:"];
602 if (prefix_range.location != NSNotFound)
603 [o_mrl deleteCharactersInRange: prefix_range];
605 if ([o_mrl characterAtIndex:0] == '/') {
606 [o_mi_revealInFinder setEnabled: YES];
607 [o_mm_mi_revealInFinder setEnabled: YES];
612 /* update our info-panel to reflect the new item */
613 [[[VLCMain sharedInstance] info] updatePanelWithItem:p_item->p_input];
617 - (BOOL)isSelectionEmpty
619 return [o_outline_view selectedRow] == -1;
622 - (void)updateRowSelection
625 playlist_t *p_playlist = pl_Get(VLCIntf);
626 playlist_item_t *p_item, *p_temp_item;
627 NSMutableArray *o_array = [NSMutableArray array];
630 p_item = playlist_CurrentPlayingItem(p_playlist);
631 if (p_item == NULL) {
636 p_temp_item = p_item;
637 while(p_temp_item->p_parent) {
638 [o_array insertObject: [NSValue valueWithPointer: p_temp_item] atIndex: 0];
639 p_temp_item = p_temp_item->p_parent;
643 NSUInteger count = [o_array count];
644 for (NSUInteger j = 0; j < count - 1; j++) {
646 if ((o_item = [o_outline_dict objectForKey:
647 [NSString stringWithFormat: @"%p",
648 [[o_array objectAtIndex:j] pointerValue]]]) != nil) {
649 [o_outline_view expandItem: o_item];
653 id o_item = [o_outline_dict objectForKey:[NSString stringWithFormat: @"%p", p_item]];
654 NSInteger i_index = [o_outline_view rowForItem:o_item];
655 [o_outline_view selectRowIndexes:[NSIndexSet indexSetWithIndex:i_index] byExtendingSelection:NO];
656 [o_outline_view setNeedsDisplay:YES];
659 /* Check if p_item is a child of p_node recursively. We need to check the item
660 existence first since OSX sometimes tries to redraw items that have been
661 deleted. We don't do it when not required since this verification takes
662 quite a long time on big playlists (yes, pretty hacky). */
664 - (BOOL)isItem: (playlist_item_t *)p_item inNode: (playlist_item_t *)p_node checkItemExistence:(BOOL)b_check locked:(BOOL)b_locked
666 playlist_t * p_playlist = pl_Get(VLCIntf);
667 playlist_item_t *p_temp_item = p_item;
672 if (p_node == p_item)
675 if (p_node->i_children < 1)
680 if (!b_locked) PL_LOCK;
683 /* Since outlineView: willDisplayCell:... may call this function with
684 p_items that don't exist anymore, first check if the item is still
685 in the playlist. Any cleaner solution welcomed. */
686 for (i = 0; i < p_playlist->all_items.i_size; i++) {
687 if (ARRAY_VAL(p_playlist->all_items, i) == p_item)
689 else if (i == p_playlist->all_items.i_size - 1)
691 if (!b_locked) PL_UNLOCK;
698 p_temp_item = p_temp_item->p_parent;
699 if (p_temp_item == p_node) {
700 if (!b_locked) PL_UNLOCK;
704 if (!b_locked) PL_UNLOCK;
709 /* This method is useful for instance to remove the selected children of an
710 already selected node */
711 - (void)removeItemsFrom:(id)o_items ifChildrenOf:(id)o_nodes
713 NSUInteger itemCount = [o_items count];
714 NSUInteger nodeCount = [o_nodes count];
715 for (NSUInteger i = 0 ; i < itemCount ; i++) {
716 for (NSUInteger j = 0 ; j < nodeCount ; j++) {
717 if (o_items == o_nodes) {
718 if (j == i) continue;
720 if ([self isItem: [[o_items objectAtIndex:i] pointerValue]
721 inNode: [[o_nodes objectAtIndex:j] pointerValue]
722 checkItemExistence: NO locked:NO]) {
723 [o_items removeObjectAtIndex:i];
724 /* We need to execute the next iteration with the same index
725 since the current item has been deleted */
733 - (IBAction)savePlaylist:(id)sender
735 playlist_t * p_playlist = pl_Get(VLCIntf);
737 NSSavePanel *o_save_panel = [NSSavePanel savePanel];
738 NSString * o_name = [NSString stringWithFormat: @"%@", _NS("Untitled")];
740 [NSBundle loadNibNamed:@"PlaylistAccessoryView" owner:self];
742 [o_save_accessory_text setStringValue: _NS("File Format:")];
743 [[o_save_accessory_popup itemAtIndex:0] setTitle: _NS("Extended M3U")];
744 [[o_save_accessory_popup itemAtIndex:1] setTitle: _NS("XML Shareable Playlist Format (XSPF)")];
745 [[o_save_accessory_popup itemAtIndex:2] setTitle: _NS("HTML playlist")];
747 [o_save_panel setTitle: _NS("Save Playlist")];
748 [o_save_panel setPrompt: _NS("Save")];
749 [o_save_panel setAccessoryView: o_save_accessory_view];
750 [o_save_panel setNameFieldStringValue: o_name];
752 if ([o_save_panel runModal] == NSFileHandlingPanelOKButton) {
753 NSString *o_filename = [[o_save_panel URL] path];
755 if ([o_save_accessory_popup indexOfSelectedItem] == 0) {
756 NSString * o_real_filename;
758 range.location = [o_filename length] - [@".m3u" length];
759 range.length = [@".m3u" length];
761 if ([o_filename compare:@".m3u" options: NSCaseInsensitiveSearch range: range] != NSOrderedSame)
762 o_real_filename = [NSString stringWithFormat: @"%@.m3u", o_filename];
764 o_real_filename = o_filename;
766 playlist_Export(p_playlist,
767 [o_real_filename fileSystemRepresentation],
768 p_playlist->p_local_category, "export-m3u");
769 } else if ([o_save_accessory_popup indexOfSelectedItem] == 1) {
770 NSString * o_real_filename;
772 range.location = [o_filename length] - [@".xspf" length];
773 range.length = [@".xspf" length];
775 if ([o_filename compare:@".xspf" options: NSCaseInsensitiveSearch range: range] != NSOrderedSame)
776 o_real_filename = [NSString stringWithFormat: @"%@.xspf", o_filename];
778 o_real_filename = o_filename;
780 playlist_Export(p_playlist,
781 [o_real_filename fileSystemRepresentation],
782 p_playlist->p_local_category, "export-xspf");
784 NSString * o_real_filename;
786 range.location = [o_filename length] - [@".html" length];
787 range.length = [@".html" length];
789 if ([o_filename compare:@".html" options: NSCaseInsensitiveSearch range: range] != NSOrderedSame)
790 o_real_filename = [NSString stringWithFormat: @"%@.html", o_filename];
792 o_real_filename = o_filename;
794 playlist_Export(p_playlist,
795 [o_real_filename fileSystemRepresentation],
796 p_playlist->p_local_category, "export-html");
801 /* When called retrieves the selected outlineview row and plays that node or item */
802 - (IBAction)playItem:(id)sender
804 intf_thread_t * p_intf = VLCIntf;
805 playlist_t * p_playlist = pl_Get(p_intf);
807 playlist_item_t *p_item;
808 playlist_item_t *p_node = NULL;
810 // ignore clicks on column header when handling double action
811 if (sender != nil && [o_outline_view clickedRow] == -1 && sender != o_mi_play)
814 p_item = [[o_outline_view itemAtRow:[o_outline_view selectedRow]] pointerValue];
818 if (p_item->i_children == -1) {
819 p_node = p_item->p_parent;
822 if (p_node->i_children > 0 && p_node->pp_children[0]->i_children == -1)
823 p_item = p_node->pp_children[0];
827 playlist_Control(p_playlist, PLAYLIST_VIEWPLAY, pl_Locked, p_node, p_item);
832 - (IBAction)revealItemInFinder:(id)sender
834 NSIndexSet * selectedRows = [o_outline_view selectedRowIndexes];
835 NSUInteger count = [selectedRows count];
836 NSUInteger indexes[count];
837 [selectedRows getIndexes:indexes maxCount:count inIndexRange:nil];
839 NSMutableString * o_mrl;
840 playlist_item_t *p_item;
841 for (NSUInteger i = 0; i < count; i++) {
842 p_item = [[o_outline_view itemAtRow:indexes[i]] pointerValue];
844 if (! p_item || !p_item->p_input)
847 char * psz_url = decode_URI(input_item_GetURI(p_item->p_input));
848 o_mrl = [[NSMutableString alloc] initWithString: [NSString stringWithUTF8String:psz_url ? psz_url : ""]];
852 /* perform some checks whether it is a file and if it is local at all... */
853 if ([o_mrl length] > 0) {
854 NSRange prefix_range = [o_mrl rangeOfString: @"file:"];
855 if (prefix_range.location != NSNotFound)
856 [o_mrl deleteCharactersInRange: prefix_range];
858 if ([o_mrl characterAtIndex:0] == '/')
859 [[NSWorkspace sharedWorkspace] selectFile: o_mrl inFileViewerRootedAtPath: o_mrl];
866 /* When called retrieves the selected outlineview row and plays that node or item */
867 - (IBAction)preparseItem:(id)sender
870 NSIndexSet *o_selected_indexes;
871 intf_thread_t * p_intf = VLCIntf;
872 playlist_t * p_playlist = pl_Get(p_intf);
873 playlist_item_t *p_item = NULL;
875 o_selected_indexes = [o_outline_view selectedRowIndexes];
876 i_count = [o_selected_indexes count];
878 NSUInteger indexes[i_count];
879 [o_selected_indexes getIndexes:indexes maxCount:i_count inIndexRange:nil];
880 for (int i = 0; i < i_count; i++) {
881 p_item = [[o_outline_view itemAtRow:indexes[i]] pointerValue];
882 [o_outline_view deselectRow: indexes[i]];
885 if (p_item->i_children == -1)
886 libvlc_MetaRequest(p_intf->p_libvlc, p_item->p_input);
888 msg_Dbg(p_intf, "preparsing nodes not implemented");
891 [self playlistUpdated];
894 - (IBAction)downloadCoverArt:(id)sender
897 NSIndexSet *o_selected_indexes;
898 intf_thread_t * p_intf = VLCIntf;
899 playlist_t * p_playlist = pl_Get(p_intf);
900 playlist_item_t *p_item = NULL;
902 o_selected_indexes = [o_outline_view selectedRowIndexes];
903 i_count = [o_selected_indexes count];
905 NSUInteger indexes[i_count];
906 [o_selected_indexes getIndexes:indexes maxCount:i_count inIndexRange:nil];
907 for (int i = 0; i < i_count; i++) {
908 p_item = [[o_outline_view itemAtRow: indexes[i]] pointerValue];
910 if (p_item && p_item->i_children == -1)
911 libvlc_ArtRequest(p_intf->p_libvlc, p_item->p_input);
913 [self playlistUpdated];
916 - (IBAction)selectAll:(id)sender
918 [o_outline_view selectAll: nil];
921 - (IBAction)showInfoPanel:(id)sender
923 [[[VLCMain sharedInstance] info] initPanel];
926 - (IBAction)deleteItem:(id)sender
929 NSIndexSet *o_selected_indexes;
930 playlist_t * p_playlist;
931 intf_thread_t * p_intf = VLCIntf;
933 o_selected_indexes = [o_outline_view selectedRowIndexes];
934 i_count = [o_selected_indexes count];
935 retainedRowSelection = [o_selected_indexes firstIndex];
937 p_playlist = pl_Get(p_intf);
939 NSUInteger indexes[i_count];
940 if (i_count == [o_outline_view numberOfRows]) {
942 playlist_NodeDelete(p_playlist, [self currentPlaylistRoot], true, false);
944 [self playlistUpdated];
947 [o_selected_indexes getIndexes:indexes maxCount:i_count inIndexRange:nil];
948 for (int i = 0; i < i_count; i++) {
949 id o_item = [o_outline_view itemAtRow: indexes[i]];
950 [o_outline_view deselectRow: indexes[i]];
953 playlist_item_t *p_item = [o_item pointerValue];
954 if (!p_item || !p_item->p_input) {
959 if (p_item->i_children != -1) {
960 //is a node and not an item
961 if (playlist_Status(p_playlist) != PLAYLIST_STOPPED &&
962 [self isItem: playlist_CurrentPlayingItem(p_playlist) inNode: ((playlist_item_t *)[o_item pointerValue])
963 checkItemExistence: NO locked:YES] == YES)
964 // if current item is in selected node and is playing then stop playlist
965 playlist_Control(p_playlist, PLAYLIST_STOP, pl_Locked);
967 playlist_NodeDelete(p_playlist, p_item, true, false);
969 playlist_DeleteFromInput(p_playlist, p_item->p_input, pl_Locked);
972 [o_outline_dict removeObjectForKey:[NSString stringWithFormat:@"%p", [o_item pointerValue]]];
976 [self playlistUpdated];
979 - (IBAction)sortNodeByName:(id)sender
981 [self sortNode: SORT_TITLE];
984 - (IBAction)sortNodeByAuthor:(id)sender
986 [self sortNode: SORT_ARTIST];
989 - (void)sortNode:(int)i_mode
991 playlist_t * p_playlist = pl_Get(VLCIntf);
992 playlist_item_t * p_item;
994 if ([o_outline_view selectedRow] > -1) {
995 p_item = [[o_outline_view itemAtRow: [o_outline_view selectedRow]] pointerValue];
999 p_item = [self currentPlaylistRoot]; // If no item is selected, sort the whole playlist
1002 if (p_item->i_children > -1) // the item is a node
1003 playlist_RecursiveNodeSort(p_playlist, p_item, i_mode, ORDER_NORMAL);
1005 playlist_RecursiveNodeSort(p_playlist, p_item->p_parent, i_mode, ORDER_NORMAL);
1008 [self playlistUpdated];
1011 - (input_item_t *)createItem:(NSDictionary *)o_one_item
1013 intf_thread_t * p_intf = VLCIntf;
1014 playlist_t * p_playlist = pl_Get(p_intf);
1016 input_item_t *p_input;
1017 BOOL b_rem = FALSE, b_dir = FALSE, b_writable = FALSE;
1018 NSString *o_uri, *o_name, *o_path;
1024 o_uri = (NSString *)[o_one_item objectForKey: @"ITEM_URL"];
1025 o_nsurl = [NSURL URLWithString: o_uri];
1026 o_path = [o_nsurl path];
1027 o_name = (NSString *)[o_one_item objectForKey: @"ITEM_NAME"];
1028 o_options = (NSArray *)[o_one_item objectForKey: @"ITEM_OPTIONS"];
1030 if ([[NSFileManager defaultManager] fileExistsAtPath:o_path isDirectory:&b_dir] && b_dir &&
1031 [[NSWorkspace sharedWorkspace] getFileSystemInfoForPath:o_path isRemovable: &b_rem
1032 isWritable:&b_writable isUnmountable:NULL description:NULL type:NULL] && b_rem && !b_writable && [o_nsurl isFileURL]) {
1033 id o_vlc_open = [[VLCMain sharedInstance] open];
1035 NSString *diskType = [o_vlc_open getVolumeTypeFromMountPath: o_path];
1036 msg_Dbg(p_intf, "detected optical media of type %s in the file input", [diskType UTF8String]);
1038 if ([diskType isEqualToString: kVLCMediaDVD])
1039 o_uri = [NSString stringWithFormat: @"dvdnav://%@", [o_vlc_open getBSDNodeFromMountPath: o_path]];
1040 else if ([diskType isEqualToString: kVLCMediaVideoTSFolder])
1041 o_uri = [NSString stringWithFormat: @"dvdnav://%@", o_path];
1042 else if ([diskType isEqualToString: kVLCMediaAudioCD])
1043 o_uri = [NSString stringWithFormat: @"cdda://%@", [o_vlc_open getBSDNodeFromMountPath: o_path]];
1044 else if ([diskType isEqualToString: kVLCMediaVCD])
1045 o_uri = [NSString stringWithFormat: @"vcd://%@#0:0", [o_vlc_open getBSDNodeFromMountPath: o_path]];
1046 else if ([diskType isEqualToString: kVLCMediaSVCD])
1047 o_uri = [NSString stringWithFormat: @"vcd://%@@0:0", [o_vlc_open getBSDNodeFromMountPath: o_path]];
1048 else if ([diskType isEqualToString: kVLCMediaBD] || [diskType isEqualToString: kVLCMediaBDMVFolder])
1049 o_uri = [NSString stringWithFormat: @"bluray://%@", o_path];
1051 msg_Warn(VLCIntf, "unknown disk type, treating %s as regular input", [o_path UTF8String]);
1053 p_input = input_item_New([o_uri UTF8String], [[[NSFileManager defaultManager] displayNameAtPath: o_path] UTF8String]);
1056 p_input = input_item_New([o_uri fileSystemRepresentation], o_name ? [o_name UTF8String] : NULL);
1062 NSUInteger count = [o_options count];
1063 for (NSUInteger i = 0; i < count; i++)
1064 input_item_AddOption(p_input, [[o_options objectAtIndex:i] UTF8String], VLC_INPUT_OPTION_TRUSTED);
1067 /* Recent documents menu */
1068 if (o_nsurl != nil && (BOOL)config_GetInt(p_playlist, "macosx-recentitems") == YES)
1069 [[NSDocumentController sharedDocumentController] noteNewRecentDocumentURL: o_nsurl];
1074 - (void)appendArray:(NSArray*)o_array atPos:(int)i_position enqueue:(BOOL)b_enqueue
1076 playlist_t * p_playlist = pl_Get(VLCIntf);
1077 NSUInteger count = [o_array count];
1078 BOOL b_usingPlaylist;
1079 if ([self currentPlaylistRoot] == p_playlist->p_ml_category)
1080 b_usingPlaylist = NO;
1082 b_usingPlaylist = YES;
1085 for (NSUInteger i_item = 0; i_item < count; i_item++) {
1086 input_item_t *p_input;
1087 NSDictionary *o_one_item;
1090 o_one_item = [o_array objectAtIndex:i_item];
1091 p_input = [self createItem: o_one_item];
1096 int returnValue = playlist_AddInput(p_playlist, p_input, PLAYLIST_INSERT, i_position == -1 ? PLAYLIST_END : i_position + i_item, b_usingPlaylist, pl_Locked);
1097 if (returnValue != VLC_SUCCESS) {
1098 vlc_gc_decref(p_input);
1102 if (i_item == 0 && !b_enqueue) {
1103 playlist_item_t *p_item = playlist_ItemGetByInput(p_playlist, p_input);
1104 playlist_Control(p_playlist, PLAYLIST_VIEWPLAY, pl_Locked, p_item->p_parent, p_item);
1107 vlc_gc_decref(p_input);
1110 [self playlistUpdated];
1113 - (void)appendNodeArray:(NSArray*)o_array inNode:(playlist_item_t *)p_node atPos:(int)i_position enqueue:(BOOL)b_enqueue
1115 playlist_t * p_playlist = pl_Get(VLCIntf);
1116 NSUInteger count = [o_array count];
1118 for (NSUInteger i_item = 0; i_item < count; i_item++) {
1119 input_item_t *p_input;
1120 NSDictionary *o_one_item;
1123 o_one_item = [o_array objectAtIndex:i_item];
1124 p_input = [self createItem: o_one_item];
1131 playlist_NodeAddInput(p_playlist, p_input, p_node,
1134 PLAYLIST_END : i_position + i_item,
1138 if (i_item == 0 && !b_enqueue) {
1139 playlist_item_t *p_item;
1140 p_item = playlist_ItemGetByInput(p_playlist, p_input);
1141 playlist_Control(p_playlist, PLAYLIST_VIEWPLAY, pl_Locked, p_node, p_item);
1144 vlc_gc_decref(p_input);
1146 [self playlistUpdated];
1149 - (NSMutableArray *)subSearchItem:(playlist_item_t *)p_item
1151 playlist_t *p_playlist = pl_Get(VLCIntf);
1152 playlist_item_t *p_selected_item;
1155 i_selected_row = [o_outline_view selectedRow];
1156 if (i_selected_row < 0)
1159 p_selected_item = (playlist_item_t *)[[o_outline_view itemAtRow: i_selected_row] pointerValue];
1161 for (NSUInteger i_current = 0; i_current < p_item->i_children ; i_current++) {
1163 NSString *o_current_name, *o_current_author;
1166 o_current_name = [NSString stringWithUTF8String:p_item->pp_children[i_current]->p_input->psz_name];
1167 psz_temp = input_item_GetInfo(p_item->p_input, _("Meta-information"),_("Artist"));
1168 o_current_author = [NSString stringWithUTF8String:psz_temp];
1172 if (p_selected_item == p_item->pp_children[i_current] && b_selected_item_met == NO)
1173 b_selected_item_met = YES;
1174 else if (p_selected_item == p_item->pp_children[i_current] && b_selected_item_met == YES)
1176 else if (b_selected_item_met == YES &&
1177 ([o_current_name rangeOfString:[o_search_field
1178 stringValue] options:NSCaseInsensitiveSearch].length ||
1179 [o_current_author rangeOfString:[o_search_field
1180 stringValue] options:NSCaseInsensitiveSearch].length))
1181 /*Adds the parent items in the result array as well, so that we can
1183 return [NSMutableArray arrayWithObject: [NSValue valueWithPointer: p_item->pp_children[i_current]]];
1185 if (p_item->pp_children[i_current]->i_children > 0) {
1186 id o_result = [self subSearchItem:
1187 p_item->pp_children[i_current]];
1188 if (o_result != NULL) {
1189 [o_result insertObject: [NSValue valueWithPointer:
1190 p_item->pp_children[i_current]] atIndex:0];
1198 - (IBAction)searchItem:(id)sender
1200 playlist_t * p_playlist = pl_Get(VLCIntf);
1205 b_selected_item_met = NO;
1207 /* First, only search after the selected item:
1208 * (b_selected_item_met = NO) */
1209 o_result = [self subSearchItem:[self currentPlaylistRoot]];
1210 if (o_result == NULL)
1211 /* If the first search failed, search again from the beginning */
1212 o_result = [self subSearchItem:[self currentPlaylistRoot]];
1214 if (o_result != NULL) {
1216 if ([[o_result objectAtIndex:0] pointerValue] == p_playlist->p_local_category)
1220 NSUInteger count = [o_result count];
1222 for (NSUInteger i = i_start ; i < count - 1 ; i++) {
1223 [o_outline_view expandItem: [o_outline_dict objectForKey:
1224 [NSString stringWithFormat: @"%p",
1225 [[o_result objectAtIndex:i] pointerValue]]]];
1227 i_row = [o_outline_view rowForItem: [o_outline_dict objectForKey:
1228 [NSString stringWithFormat: @"%p",
1229 [[o_result objectAtIndex:count - 1 ]
1233 [o_outline_view selectRowIndexes:[NSIndexSet indexSetWithIndex:i_row] byExtendingSelection:NO];
1234 [o_outline_view scrollRowToVisible: i_row];
1238 - (IBAction)recursiveExpandNode:(id)sender
1240 NSIndexSet * selectedRows = [o_outline_view selectedRowIndexes];
1241 NSUInteger count = [selectedRows count];
1242 NSUInteger indexes[count];
1243 [selectedRows getIndexes:indexes maxCount:count inIndexRange:nil];
1246 playlist_item_t *p_item;
1247 for (NSUInteger i = 0; i < count; i++) {
1248 o_item = [o_outline_view itemAtRow: indexes[i]];
1249 p_item = (playlist_item_t *)[o_item pointerValue];
1251 if (![[o_outline_view dataSource] outlineView: o_outline_view isItemExpandable: o_item])
1252 o_item = [o_outline_dict objectForKey: [NSString stringWithFormat: @"%p", p_item->p_parent]];
1254 /* We need to collapse the node first, since OSX refuses to recursively
1255 expand an already expanded node, even if children nodes are collapsed. */
1256 [o_outline_view collapseItem: o_item collapseChildren: YES];
1257 [o_outline_view expandItem: o_item expandChildren: YES];
1259 selectedRows = [o_outline_view selectedRowIndexes];
1260 [selectedRows getIndexes:indexes maxCount:count inIndexRange:nil];
1264 - (NSMenu *)menuForEvent:(NSEvent *)o_event
1266 if (!b_playlistmenu_nib_loaded)
1267 b_playlistmenu_nib_loaded = [NSBundle loadNibNamed:@"PlaylistMenu" owner:self];
1273 pt = [o_outline_view convertPoint: [o_event locationInWindow] fromView: nil];
1274 int row = [o_outline_view rowAtPoint:pt];
1275 if (row != -1 && ![[o_outline_view selectedRowIndexes] containsIndex: row])
1276 [o_outline_view selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO];
1278 b_item_sel = (row != -1 && [o_outline_view selectedRow] != -1);
1279 b_rows = [o_outline_view numberOfRows] != 0;
1281 [o_mi_play setEnabled: b_item_sel];
1282 [o_mi_delete setEnabled: b_item_sel];
1283 [o_mi_selectall setEnabled: b_rows];
1284 [o_mi_info setEnabled: b_item_sel];
1285 [o_mi_preparse setEnabled: b_item_sel];
1286 [o_mi_recursive_expand setEnabled: b_item_sel];
1287 [o_mi_sort_name setEnabled: b_item_sel];
1288 [o_mi_sort_author setEnabled: b_item_sel];
1293 - (void)outlineView: (NSOutlineView *)o_tv didClickTableColumn:(NSTableColumn *)o_tc
1295 int i_mode, i_type = 0;
1296 intf_thread_t *p_intf = VLCIntf;
1297 NSString * o_identifier = [o_tc identifier];
1299 playlist_t *p_playlist = pl_Get(p_intf);
1301 if ([o_identifier isEqualToString:TRACKNUM_COLUMN])
1302 i_mode = SORT_TRACK_NUMBER;
1303 else if ([o_identifier isEqualToString:TITLE_COLUMN])
1304 i_mode = SORT_TITLE;
1305 else if ([o_identifier isEqualToString:ARTIST_COLUMN])
1306 i_mode = SORT_ARTIST;
1307 else if ([o_identifier isEqualToString:GENRE_COLUMN])
1308 i_mode = SORT_GENRE;
1309 else if ([o_identifier isEqualToString:DURATION_COLUMN])
1310 i_mode = SORT_DURATION;
1311 else if ([o_identifier isEqualToString:ALBUM_COLUMN])
1312 i_mode = SORT_ALBUM;
1313 else if ([o_identifier isEqualToString:DESCRIPTION_COLUMN])
1314 i_mode = SORT_DESCRIPTION;
1315 else if ([o_identifier isEqualToString:URI_COLUMN])
1320 if (o_tc_sortColumn == o_tc)
1321 b_isSortDescending = !b_isSortDescending;
1323 b_isSortDescending = false;
1325 if (b_isSortDescending)
1326 i_type = ORDER_REVERSE;
1328 i_type = ORDER_NORMAL;
1331 playlist_RecursiveNodeSort(p_playlist, [self currentPlaylistRoot], i_mode, i_type);
1334 [self playlistUpdated];
1336 o_tc_sortColumn = o_tc;
1337 [o_outline_view setHighlightedTableColumn:o_tc];
1339 if (b_isSortDescending)
1340 [o_outline_view setIndicatorImage:o_descendingSortingImage inTableColumn:o_tc];
1342 [o_outline_view setIndicatorImage:o_ascendingSortingImage inTableColumn:o_tc];
1346 - (void)outlineView:(NSOutlineView *)outlineView
1347 willDisplayCell:(id)cell
1348 forTableColumn:(NSTableColumn *)tableColumn
1351 /* this method can be called when VLC is already dead, hence the extra checks */
1352 intf_thread_t * p_intf = VLCIntf;
1355 playlist_t *p_playlist = pl_Get(p_intf);
1362 o_playing_item = [o_outline_dict objectForKey: [NSString stringWithFormat:@"%p", playlist_CurrentPlayingItem(p_playlist)]];
1365 if ([self isItem: [o_playing_item pointerValue] inNode: [item pointerValue] checkItemExistence:YES locked:NO]
1366 || [o_playing_item isEqual: item])
1367 [cell setFont: [[NSFontManager sharedFontManager] convertFont:[cell font] toHaveTrait:NSBoldFontMask]];
1369 [cell setFont: [[NSFontManager sharedFontManager] convertFont:[cell font] toNotHaveTrait:NSBoldFontMask]];
1374 playlist_t *p_playlist = pl_Get(VLCIntf);
1379 o_playing_item = [o_outline_dict objectForKey: [NSString stringWithFormat:@"%p", playlist_CurrentPlayingItem(p_playlist)]];
1382 return o_playing_item;
1385 - (NSArray *)draggedItems
1387 return [[o_nodes_array arrayByAddingObjectsFromArray: o_items_array] retain];
1390 - (void)setColumn: (NSString *)o_column state: (NSInteger)i_state translationDict:(NSDictionary *)o_dict
1392 NSTableColumn * o_work_tc;
1394 if (i_state == NSOnState) {
1395 o_work_tc = [[NSTableColumn alloc] initWithIdentifier: o_column];
1396 [o_work_tc setEditable: NO];
1397 [[o_work_tc dataCell] setFont: [NSFont controlContentFontOfSize:11.]];
1399 [[o_work_tc headerCell] setStringValue: [o_dict objectForKey:o_column]];
1401 if ([o_column isEqualToString: TRACKNUM_COLUMN]) {
1402 [o_work_tc setWidth: 20.];
1403 [o_work_tc setResizingMask: NSTableColumnNoResizing];
1404 [[o_work_tc headerCell] setStringValue: @"#"];
1407 [o_outline_view addTableColumn: o_work_tc];
1408 [o_work_tc release];
1409 [o_outline_view reloadData];
1410 [o_outline_view setNeedsDisplay: YES];
1413 [o_outline_view removeTableColumn: [o_outline_view tableColumnWithIdentifier: o_column]];
1415 [o_outline_view setOutlineTableColumn: [o_outline_view tableColumnWithIdentifier:TITLE_COLUMN]];
1418 - (void)saveTableColumns
1420 NSMutableArray * o_arrayToSave = [[NSMutableArray alloc] init];
1421 NSArray * o_columns = [[NSArray alloc] initWithArray:[o_outline_view tableColumns]];
1422 NSUInteger count = [o_columns count];
1423 NSTableColumn * o_currentColumn;
1424 for (NSUInteger i = 0; i < count; i++) {
1425 o_currentColumn = [o_columns objectAtIndex:i];
1426 [o_arrayToSave addObject:[NSArray arrayWithObjects:[o_currentColumn identifier], [NSNumber numberWithFloat:[o_currentColumn width]], nil]];
1428 [[NSUserDefaults standardUserDefaults] setObject: o_arrayToSave forKey:@"PlaylistColumnSelection"];
1429 [[NSUserDefaults standardUserDefaults] synchronize];
1430 [o_columns release];
1431 [o_arrayToSave release];
1436 @implementation VLCPlaylist (NSOutlineViewDataSource)
1438 - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item
1440 id o_value = [super outlineView: outlineView child: index ofItem: item];
1442 [o_outline_dict setObject:o_value forKey:[NSString stringWithFormat:@"%p", [o_value pointerValue]]];
1446 /* Required for drag & drop and reordering */
1447 - (BOOL)outlineView:(NSOutlineView *)outlineView writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pboard
1449 playlist_t *p_playlist = pl_Get(VLCIntf);
1451 /* First remove the items that were moved during the last drag & drop
1453 [o_items_array removeAllObjects];
1454 [o_nodes_array removeAllObjects];
1456 NSUInteger itemCount = [items count];
1458 for (NSUInteger i = 0 ; i < itemCount ; i++) {
1459 id o_item = [items objectAtIndex:i];
1461 /* Fill the items and nodes to move in 2 different arrays */
1462 if (((playlist_item_t *)[o_item pointerValue])->i_children > 0)
1463 [o_nodes_array addObject: o_item];
1465 [o_items_array addObject: o_item];
1468 /* Now we need to check if there are selected items that are in already
1469 selected nodes. In that case, we only want to move the nodes */
1470 [self removeItemsFrom: o_nodes_array ifChildrenOf: o_nodes_array];
1471 [self removeItemsFrom: o_items_array ifChildrenOf: o_nodes_array];
1473 /* We add the "VLCPlaylistItemPboardType" type to be able to recognize
1474 a Drop operation coming from the playlist. */
1476 [pboard declareTypes: [NSArray arrayWithObject:@"VLCPlaylistItemPboardType"] owner: self];
1477 [pboard setData:[NSData data] forType:@"VLCPlaylistItemPboardType"];
1482 - (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id <NSDraggingInfo>)info proposedItem:(id)item proposedChildIndex:(NSInteger)index
1484 playlist_t *p_playlist = pl_Get(VLCIntf);
1485 NSPasteboard *o_pasteboard = [info draggingPasteboard];
1487 if (!p_playlist) return NSDragOperationNone;
1489 /* Dropping ON items is not allowed if item is not a node */
1491 if (index == NSOutlineViewDropOnItemIndex &&
1492 ((playlist_item_t *)[item pointerValue])->i_children == -1) {
1493 return NSDragOperationNone;
1497 /* We refuse to drop an item in anything else than a child of the General
1498 Node. We still accept items that would be root nodes of the outlineview
1499 however, to allow drop in an empty playlist. */
1500 if (!(([self isItem: [item pointerValue] inNode: p_playlist->p_local_category checkItemExistence: NO locked: NO] ||
1501 (var_CreateGetBool(p_playlist, "media-library") && [self isItem: [item pointerValue] inNode: p_playlist->p_ml_category checkItemExistence: NO locked: NO])) || item == nil)) {
1502 return NSDragOperationNone;
1505 /* Drop from the Playlist */
1506 if ([[o_pasteboard types] containsObject: @"VLCPlaylistItemPboardType"]) {
1507 NSUInteger count = [o_nodes_array count];
1508 for (NSUInteger i = 0 ; i < count ; i++) {
1509 /* We refuse to Drop in a child of an item we are moving */
1510 if ([self isItem: [item pointerValue] inNode: [[o_nodes_array objectAtIndex:i] pointerValue] checkItemExistence: NO locked:NO]) {
1511 return NSDragOperationNone;
1514 return NSDragOperationMove;
1516 /* Drop from the Finder */
1517 else if ([[o_pasteboard types] containsObject: NSFilenamesPboardType]) {
1518 return NSDragOperationGeneric;
1520 return NSDragOperationNone;
1523 - (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id <NSDraggingInfo>)info item:(id)item childIndex:(NSInteger)index
1525 playlist_t * p_playlist = pl_Get(VLCIntf);
1526 NSPasteboard *o_pasteboard = [info draggingPasteboard];
1528 /* Drag & Drop inside the playlist */
1529 if ([[o_pasteboard types] containsObject: @"VLCPlaylistItemPboardType"]) {
1530 if (index == -1) // this is no valid target, sanitize to top of table
1534 playlist_item_t *p_new_parent, *p_item = NULL;
1535 NSArray *o_all_items = [o_nodes_array arrayByAddingObjectsFromArray: o_items_array];
1536 /* If the item is to be dropped as root item of the outline, make it a
1537 child of the respective general node, if is either the pl or the ml
1538 Else, choose the proposed parent as parent. */
1540 if ([self currentPlaylistRoot] == p_playlist->p_local_category || [self currentPlaylistRoot] == p_playlist->p_ml_category)
1541 p_new_parent = [self currentPlaylistRoot];
1546 p_new_parent = [item pointerValue];
1548 /* Make sure the proposed parent is a node.
1549 (This should never be true) */
1550 if (p_new_parent->i_children < 0)
1553 NSUInteger count = [o_all_items count];
1557 playlist_item_t **pp_items = (playlist_item_t **)calloc(count, sizeof(playlist_item_t*));
1563 for (NSUInteger i = 0; i < count; i++) {
1564 p_item = [[o_all_items objectAtIndex:i] pointerValue];
1566 pp_items[j++] = p_item;
1569 if (j == 0 || playlist_TreeMoveMany(p_playlist, j, pp_items, p_new_parent, index) != VLC_SUCCESS) {
1578 [self playlistUpdated];
1579 i_row = [o_outline_view rowForItem:[o_outline_dict objectForKey:[NSString stringWithFormat: @"%p", [[o_all_items objectAtIndex:0] pointerValue]]]];
1582 i_row = [o_outline_view rowForItem:[o_outline_dict objectForKey:[NSString stringWithFormat: @"%p", p_new_parent]]];
1584 [o_outline_view deselectAll: self];
1585 [o_outline_view selectRowIndexes:[NSIndexSet indexSetWithIndex:i_row] byExtendingSelection:NO];
1586 [o_outline_view scrollRowToVisible: i_row];
1591 else if ([[o_pasteboard types] containsObject: NSFilenamesPboardType]) {
1592 if ([self currentPlaylistRoot] != p_playlist->p_local_category && [self currentPlaylistRoot] != p_playlist->p_ml_category)
1595 playlist_item_t *p_node = [item pointerValue];
1597 NSArray *o_values = [[o_pasteboard propertyListForType: NSFilenamesPboardType]
1598 sortedArrayUsingSelector: @selector(caseInsensitiveCompare:)];
1599 NSUInteger count = [o_values count];
1600 NSMutableArray *o_array = [NSMutableArray arrayWithCapacity:count];
1601 input_thread_t * p_input = pl_CurrentInput(VLCIntf);
1603 if (count == 1 && p_input) {
1604 int i_result = input_AddSubtitleOSD(p_input, vlc_path2uri([[o_values objectAtIndex:0] UTF8String], NULL), true, true);
1605 vlc_object_release(p_input);
1606 if (i_result == VLC_SUCCESS)
1610 vlc_object_release(p_input);
1612 for (NSUInteger i = 0; i < count; i++) {
1613 NSDictionary *o_dic;
1614 char *psz_uri = vlc_path2uri([[o_values objectAtIndex:i] UTF8String], NULL);
1618 o_dic = [NSDictionary dictionaryWithObject:[NSString stringWithCString:psz_uri encoding:NSUTF8StringEncoding] forKey:@"ITEM_URL"];
1622 [o_array addObject: o_dic];
1626 [self appendArray:o_array atPos:index enqueue: YES];
1628 assert(p_node->i_children != -1);
1629 [self appendNodeArray:o_array inNode: p_node atPos:index enqueue:YES];