1 /*****************************************************************************
2 * playlist.m: MacOS X interface module
3 *****************************************************************************
4 * Copyright (C) 2002-2004 VideoLAN
7 * Authors: Jon Lech Johansen <jon-vl@nanocrew.net>
8 * Derk-Jan Hartman <hartman at videolan dot org>
9 * Benjamin Pracht <bigben at videolab dot org>
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
21 * You should have received a copy of the GNU General Public License
22 * along with this program; if not, write to the Free Software
23 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
24 *****************************************************************************/
27 * connect delegates, actions and outlets in IB
28 * implement delete by backspace
29 * implement playlist item rightclick menu
31 * add 'icons' for different types of nodes? (http://www.cocoadev.com/index.pl?IconAndTextInTableCell)
32 * create a new 'playlist toggle' that hides the playlist and in effect give you the old controller
33 * create a new search field build with pictures from the 'regular' search field, so it can be emulated on 10.2
34 * create toggle buttons for the shuffle, repeat one, repeat all functions.
35 * implement drag and drop and item reordering.
36 * reimplement enable/disable item
37 * create a new 'tool' button (see the gear button in the Finder window) for 'actions'
38 (adding service discovery, other views, new node/playlist, save node/playlist) stuff like that
43 /*****************************************************************************
45 *****************************************************************************/
46 #include <stdlib.h> /* malloc(), free() */
47 #include <sys/param.h> /* for MAXPATHLEN */
50 #include <sys/mount.h>
58 /*****************************************************************************
59 * VLCPlaylistView implementation
60 *****************************************************************************/
61 @implementation VLCPlaylistView
63 - (NSMenu *)menuForEvent:(NSEvent *)o_event
65 return( [[self delegate] menuForEvent: o_event] );
70 [[self delegate] initDict];
74 - (bool)isItem:(playlist_item_t *)p_item inNode:(playlist_item_t *)p_node
77 playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
79 if ( p_playlist == NULL )
84 for (i = 0 ; i < p_node->i_children ; i++)
86 if (p_node->pp_children[i]->i_children > 0)
88 if ([self isItem: p_item inNode:p_node->pp_children[i]] == YES)
90 vlc_object_release(p_playlist);
94 else if (p_node->pp_children[i] == p_item)
96 vlc_object_release(p_playlist);
101 vlc_object_release(p_playlist);
105 - (void)keyDown:(NSEvent *)o_event
109 NSMutableArray *o_to_delete;
112 playlist_t * p_playlist;
113 intf_thread_t * p_intf = VLCIntf;
114 msg_Dbg( p_intf, "KEYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY");
115 if( [[o_event characters] length] )
117 key = [[o_event characters] characterAtIndex: 0];
120 p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
123 if ( p_playlist == NULL )
130 case NSDeleteCharacter:
131 case NSDeleteFunctionKey:
132 case NSDeleteCharFunctionKey:
133 case NSBackspaceCharacter:
134 o_to_delete = [NSMutableArray arrayWithArray:[[self selectedRowEnumerator] allObjects]];
135 c = [o_to_delete count];
137 for( i = 0; i < c; i++ ) {
138 playlist_item_t * p_item;
139 o_number = [o_to_delete lastObject];
140 i_row = [o_number intValue];
142 [o_to_delete removeObject: o_number];
143 [self deselectRow: i_row];
144 p_item = (playlist_item_t *)[[self itemAtRow: i_row]pointerValue];
145 if (p_item->i_children > -1)
147 if ([self isItem:p_playlist->status.p_item inNode: p_item]
148 == YES && p_playlist->status.i_status)
150 playlist_Stop( p_playlist );
152 playlist_NodeDelete( p_playlist, p_item, VLC_TRUE);
156 if( p_playlist->status.p_item == [[self itemAtRow: i_row]
157 pointerValue] && p_playlist->status.i_status )
159 playlist_Stop( p_playlist );
161 playlist_Delete( p_playlist, p_item->input.i_id );
163 [self reloadPlaylist];
168 [super keyDown: o_event];
172 if( p_playlist != NULL )
174 vlc_object_release( p_playlist );
180 /*****************************************************************************
181 * VLCPlaylist implementation
182 *****************************************************************************/
183 @implementation VLCPlaylist
197 [o_outline_view setTarget: self];
198 [o_outline_view setDelegate: self];
199 [o_outline_view setDataSource: self];
201 [o_outline_view setDoubleAction: @selector(playItem:)];
203 [o_outline_view registerForDraggedTypes:
204 [NSArray arrayWithObjects: NSFilenamesPboardType, nil]];
205 [o_outline_view setIntercellSpacing: NSMakeSize (0.0, 1.0)];
207 /* We need to check whether _defaultTableHeaderSortImage exists, since it
208 belongs to an Apple hidden private API, and then can "disapear" at any time*/
210 if( [[NSOutlineView class] respondsToSelector:@selector(_defaultTableHeaderSortImage)] )
212 o_ascendingSortingImage = [[NSOutlineView class] _defaultTableHeaderSortImage];
216 o_ascendingSortingImage = nil;
219 if( [[NSOutlineView class] respondsToSelector:@selector(_defaultTableHeaderReverseSortImage)] )
221 o_descendingSortingImage = [[NSOutlineView class] _defaultTableHeaderReverseSortImage];
225 o_descendingSortingImage = nil;
228 o_outline_dict = [[NSMutableDictionary alloc] init];
231 //[self playlistUpdated];
236 [o_mi_save_playlist setTitle: _NS("Save Playlist...")];
238 [o_mi_play setTitle: _NS("Play")];
239 [o_mi_delete setTitle: _NS("Delete")];
240 [o_mi_selectall setTitle: _NS("Select All")];
241 [o_mi_toggleItemsEnabled setTitle: _NS("Item Enabled")];
242 [o_mi_enableGroup setTitle: _NS("Enable all group items")];
243 [o_mi_disableGroup setTitle: _NS("Disable all group items")];
244 [o_mi_info setTitle: _NS("Properties")];
246 [[o_tc_name headerCell] setStringValue:_NS("Name")];
247 [[o_tc_author headerCell] setStringValue:_NS("Author")];
248 [[o_tc_duration headerCell] setStringValue:_NS("Duration")];
249 [o_status_field setStringValue: [NSString stringWithFormat:
250 _NS("0 items in playlist")]];
252 [o_random_ckb setTitle: _NS("Random")];
254 [o_search_button setTitle: _NS("Search")];
256 [o_btn_playlist setToolTip: _NS("Playlist")];
257 [[o_loop_popup itemAtIndex:0] setTitle: _NS("Standard Play")];
258 [[o_loop_popup itemAtIndex:1] setTitle: _NS("Repeat One")];
259 [[o_loop_popup itemAtIndex:2] setTitle: _NS("Repeat All")];
262 - (void)playlistUpdated
264 [o_outline_view reloadData];
267 - (IBAction)playItem:(id)sender
269 intf_thread_t * p_intf = VLCIntf;
270 playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
273 if( p_playlist != NULL )
275 playlist_item_t *p_item;
276 playlist_item_t *p_node = NULL;
279 p_item = [[o_outline_view itemAtRow:[o_outline_view selectedRow]] pointerValue];
283 if (p_item->i_children == -1)
285 for (i = 0 ; i < p_item->i_parents ; i++)
287 if (p_item->pp_parents[i]->i_view == VIEW_SIMPLE)
289 p_node = p_item->pp_parents[i]->p_parent;
296 if (p_node->pp_children[0]->i_children == -1 &&
297 p_node->i_children > 0)
299 p_item = p_node->pp_children[0];
307 // p_view = playlist_ViewFind( p_playlist, VIEW_SIMPLE );
310 playlist_Control( p_playlist, PLAYLIST_VIEWPLAY, VIEW_SIMPLE, p_node, p_item );
311 // playlist_Control( p_playlist, PLAYLIST_VIEWPLAY, VIEW_SIMPLE, p_view ? p_view->p_root : NULL, p_item );
313 vlc_object_release( p_playlist );
317 - (void)appendArray:(NSArray*)o_array atPos:(int)i_position enqueue:(BOOL)b_enqueue
320 intf_thread_t * p_intf = VLCIntf;
321 playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
324 if( p_playlist == NULL )
329 for ( i_item = 0; i_item < (int)[o_array count]; i_item++ )
332 NSDictionary *o_one_item;
333 int j, i_total_options = 0, i_new_id = -1;
334 int i_mode = PLAYLIST_INSERT;
335 BOOL b_rem = FALSE, b_dir = FALSE;
336 NSString *o_uri, *o_name;
339 char **ppsz_options = NULL;
342 o_one_item = [o_array objectAtIndex: i_item];
343 o_uri = (NSString *)[o_one_item objectForKey: @"ITEM_URL"];
344 o_name = (NSString *)[o_one_item objectForKey: @"ITEM_NAME"];
345 o_options = (NSArray *)[o_one_item objectForKey: @"ITEM_OPTIONS"];
347 /* If no name, then make a guess */
348 if( !o_name) o_name = [[NSFileManager defaultManager] displayNameAtPath: o_uri];
350 if( [[NSFileManager defaultManager] fileExistsAtPath:o_uri isDirectory:&b_dir] && b_dir &&
351 [[NSWorkspace sharedWorkspace] getFileSystemInfoForPath: o_uri isRemovable: &b_rem
352 isWritable:NULL isUnmountable:NULL description:NULL type:NULL] && b_rem )
354 /* All of this is to make sure CD's play when you D&D them on VLC */
355 /* Converts mountpoint to a /dev file */
358 buf = (struct statfs *) malloc (sizeof(struct statfs));
359 statfs( [o_uri fileSystemRepresentation], buf );
360 psz_dev = strdup(buf->f_mntfromname);
361 o_uri = [NSString stringWithCString: psz_dev ];
364 if( o_options && [o_options count] > 0 )
366 /* Count the input options */
367 i_total_options = [o_options count];
369 /* Allocate ppsz_options */
370 for( j = 0; j < i_total_options; j++ )
373 ppsz_options = (char **)malloc( sizeof(char *) * i_total_options );
375 ppsz_options[j] = strdup([[o_options objectAtIndex:j] UTF8String]);
380 i_new_id = playlist_AddExt( p_playlist, [o_uri fileSystemRepresentation],
381 [o_name UTF8String], i_mode,
382 i_position == -1 ? PLAYLIST_END : i_position + i_item,
383 0, (ppsz_options != NULL ) ? (const char **)ppsz_options : 0, i_total_options );
386 for( j = 0; j < i_total_options; j++ )
387 free( ppsz_options[j] );
388 if( ppsz_options ) free( ppsz_options ); */
390 /* Recent documents menu */
391 o_true_file = [NSURL fileURLWithPath: o_uri];
392 if( o_true_file != nil )
394 [[NSDocumentController sharedDocumentController]
395 noteNewRecentDocumentURL: o_true_file];
398 if( i_item == 0 && !b_enqueue )
400 playlist_Goto( p_playlist, playlist_GetPositionById( p_playlist, i_new_id ) );
401 playlist_Play( p_playlist );
405 vlc_object_release( p_playlist );
408 - (IBAction)handlePopUp:(id)sender
411 intf_thread_t * p_intf = VLCIntf;
412 vlc_value_t val1,val2;
413 playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
415 if( p_playlist == NULL )
420 switch ([o_loop_popup indexOfSelectedItem])
425 var_Set( p_playlist, "loop", val1 );
427 var_Set( p_playlist, "repeat", val1 );
428 vout_OSDMessage( p_intf, DEFAULT_CHAN, _( "Repeat One" ) );
433 var_Set( p_playlist, "repeat", val1 );
435 var_Set( p_playlist, "loop", val1 );
436 vout_OSDMessage( p_intf, DEFAULT_CHAN, _( "Repeat All" ) );
440 var_Get( p_playlist, "repeat", &val1 );
441 var_Get( p_playlist, "loop", &val2 );
442 if (val1.b_bool || val2.b_bool)
445 var_Set( p_playlist, "repeat", val1 );
446 var_Set( p_playlist, "loop", val1 );
447 vout_OSDMessage( p_intf, DEFAULT_CHAN, _( "Repeat Off" ) );
451 vlc_object_release( p_playlist );
452 [self playlistUpdated];
455 - (NSMutableArray *)subSearchItem:(playlist_item_t *)p_item
457 playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
459 playlist_item_t * p_selected_item;
460 int i_current, i_selected_row;
465 i_selected_row = [o_outline_view selectedRow];
466 if (i_selected_row < 0)
469 p_selected_item = (playlist_item_t *)[[o_outline_view itemAtRow:
470 i_selected_row] pointerValue];
472 for (i_current = 0; i_current < p_item->i_children ; i_current++)
475 NSString * o_current_name, * o_current_author;
477 vlc_mutex_lock( &p_playlist->object_lock );
478 o_current_name = [NSString stringWithUTF8String:
479 p_item->pp_children[i_current]->input.psz_name];
480 psz_temp = playlist_ItemGetInfo(p_item ,_("Meta-information"),_("Author") );
481 o_current_author = [NSString stringWithUTF8String: psz_temp];
483 vlc_mutex_unlock( &p_playlist->object_lock );
485 if (p_selected_item == p_item->pp_children[i_current] &&
486 b_selected_item_met == NO)
488 b_selected_item_met = YES;
490 else if (p_selected_item == p_item->pp_children[i_current] &&
491 b_selected_item_met == YES)
493 vlc_object_release(p_playlist);
496 else if (b_selected_item_met == YES &&
497 ([o_current_name rangeOfString:[o_search_field
498 stringValue] options:NSCaseInsensitiveSearch ].length ||
499 [o_current_author rangeOfString:[o_search_field
500 stringValue] options:NSCaseInsensitiveSearch ].length))
502 vlc_object_release(p_playlist);
503 /*Adds the parent items in the result array as well, so that we can
505 return [NSMutableArray arrayWithObject: [NSValue
506 valueWithPointer: p_item->pp_children[i_current]]];
508 if (p_item->pp_children[i_current]->i_children > 0)
510 id o_result = [self subSearchItem:
511 p_item->pp_children[i_current]];
512 if (o_result != NULL)
514 vlc_object_release(p_playlist);
515 [o_result insertObject: [NSValue valueWithPointer:
516 p_item->pp_children[i_current]] atIndex:0];
521 vlc_object_release(p_playlist);
525 - (IBAction)searchItem:(id)sender
527 playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
529 playlist_view_t * p_view;
535 b_selected_item_met = NO;
537 if( p_playlist == NULL )
540 p_view = playlist_ViewFind( p_playlist, VIEW_SIMPLE );
544 /*First, only search after the selected item:*
545 *(b_selected_item_met = NO) */
546 o_result = [self subSearchItem:p_view->p_root];
547 if (o_result == NULL)
549 /* If the first search failed, search again from the beginning */
550 o_result = [self subSearchItem:p_view->p_root];
552 if (o_result != NULL)
554 for (i = 0 ; i < [o_result count] - 1 ; i++)
556 [o_outline_view expandItem: [o_outline_dict objectForKey:
557 [NSString stringWithFormat: @"%p",
558 [[o_result objectAtIndex: i] pointerValue]]]];
560 i_row = [o_outline_view rowForItem: [o_outline_dict objectForKey:
561 [NSString stringWithFormat: @"%p",
562 [[o_result objectAtIndex: [o_result count] - 1 ]
567 [o_outline_view selectRow:i_row byExtendingSelection: NO];
568 [o_outline_view scrollRowToVisible: i_row];
571 vlc_object_release(p_playlist);
577 [o_outline_dict removeAllObjects];
582 @implementation VLCPlaylist (NSOutlineViewDataSource)
584 /* return the number of children for Obj-C pointer item */ /* DONE */
585 - (int)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
588 playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
590 if( p_playlist == NULL )
596 playlist_view_t *p_view;
597 p_view = playlist_ViewFind( p_playlist, VIEW_SIMPLE );
598 if( p_view && p_view->p_root )
599 i_return = p_view->p_root->i_children;
603 playlist_item_t *p_item = (playlist_item_t *)[item pointerValue];
605 i_return = p_item->i_children;
607 vlc_object_release( p_playlist );
608 if( i_return == -1 ) i_return = 0;
609 msg_Dbg( p_playlist, "I have %d children", i_return );
613 /* return the child at index for the Obj-C pointer item */ /* DONE */
614 - (id)outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item
616 playlist_item_t *p_return = NULL;
617 playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
621 if( p_playlist == NULL )
627 playlist_view_t *p_view;
628 p_view = playlist_ViewFind( p_playlist, VIEW_SIMPLE );
629 if( p_view && index < p_view->p_root->i_children )
630 p_return = p_view->p_root->pp_children[index];
634 playlist_item_t *p_item = (playlist_item_t *)[item pointerValue];
635 if( p_item && index < p_item->i_children )
637 p_return = p_item->pp_children[index];
641 [o_status_field setStringValue: [NSString stringWithFormat:
642 _NS("%i items in playlist"), p_playlist->i_size]];
644 vlc_object_release( p_playlist );
645 msg_Dbg( p_playlist, "childitem with index %d", index );
647 o_value = [NSValue valueWithPointer: p_return];
649 [o_outline_dict setObject:o_value forKey:[NSString stringWithFormat:@"%p", p_return]];
653 /* is the item expandable */
654 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
657 playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
659 if( p_playlist == NULL )
665 playlist_view_t *p_view;
666 p_view = playlist_ViewFind( p_playlist, VIEW_SIMPLE );
667 if( p_view && p_view->p_root )
668 i_return = p_view->p_root->i_children;
672 playlist_item_t *p_item = (playlist_item_t *)[item pointerValue];
674 i_return = p_item->i_children;
676 vlc_object_release( p_playlist );
678 if( i_return == -1 || i_return == 0 )
684 /* retrieve the string values for the cells */
685 - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)o_tc byItem:(id)item
688 intf_thread_t * p_intf = VLCIntf;
689 playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
691 playlist_item_t *p_item = (playlist_item_t *)[item pointerValue];
693 if( p_playlist == NULL || p_item == NULL )
698 if( [[o_tc identifier] isEqualToString:@"1"] )
700 o_value = [NSString stringWithUTF8String:
701 p_item->input.psz_name];
702 if( o_value == NULL )
703 o_value = [NSString stringWithCString:
704 p_item->input.psz_name];
706 else if( [[o_tc identifier] isEqualToString:@"2"] )
709 psz_temp = playlist_ItemGetInfo( p_item ,_("Meta-information"),_("Artist") );
711 if( psz_temp == NULL )
715 o_value = [NSString stringWithUTF8String: psz_temp];
716 if( o_value == NULL )
718 o_value = [NSString stringWithCString: psz_temp];
723 else if( [[o_tc identifier] isEqualToString:@"3"] )
725 char psz_duration[MSTRTIME_MAX_SIZE];
726 mtime_t dur = p_item->input.i_duration;
729 secstotimestr( psz_duration, dur/1000000 );
730 o_value = [NSString stringWithUTF8String: psz_duration];
734 o_value = @"-:--:--";
738 vlc_object_release( p_playlist );
743 /* Required for drag & drop and reordering */
744 - (BOOL)outlineView:(NSOutlineView *)outlineView writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pboard
749 - (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id <NSDraggingInfo>)info proposedItem:(id)item proposedChildIndex:(int)index
751 return NSDragOperationNone;
754 /* Delegate method of NSWindow */
755 - (void)windowWillClose:(NSNotification *)aNotification
757 [o_btn_playlist setState: NSOffState];