1 /*****************************************************************************
2 * playlist.m: MacOS X interface module
3 *****************************************************************************
4 * Copyright (C) 2002-2006 the VideoLAN team
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>
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., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
24 *****************************************************************************/
27 * add 'icons' for different types of nodes? (http://www.cocoadev.com/index.pl?IconAndTextInTableCell)
28 * create toggle buttons for the shuffle, repeat one, repeat all functions.
29 * reimplement enable/disable item
30 * create a new 'tool' button (see the gear button in the Finder window) for 'actions'
31 (adding service discovery, other views, new node/playlist, save node/playlist) stuff like that
35 /*****************************************************************************
37 *****************************************************************************/
38 #include <stdlib.h> /* malloc(), free() */
39 #include <sys/param.h> /* for MAXPATHLEN */
42 #include <sys/mount.h>
48 #import "playlistinfo.h"
53 #import <vlc_interaction.h>
55 /*****************************************************************************
56 * VLCPlaylistView implementation
57 *****************************************************************************/
58 @implementation VLCPlaylistView
60 - (NSMenu *)menuForEvent:(NSEvent *)o_event
62 return( [[self delegate] menuForEvent: o_event] );
65 - (void)keyDown:(NSEvent *)o_event
69 if( [[o_event characters] length] )
71 key = [[o_event characters] characterAtIndex: 0];
76 case NSDeleteCharacter:
77 case NSDeleteFunctionKey:
78 case NSDeleteCharFunctionKey:
79 case NSBackspaceCharacter:
80 [[self delegate] deleteItem:self];
83 case NSEnterCharacter:
84 case NSCarriageReturnCharacter:
85 [(VLCPlaylist *)[[VLCMain sharedInstance] getPlaylist]
90 [super keyDown: o_event];
98 /*****************************************************************************
99 * VLCPlaylistCommon implementation
101 * This class the superclass of the VLCPlaylist and VLCPlaylistWizard.
102 * It contains the common methods and elements of these 2 entities.
103 *****************************************************************************/
104 @implementation VLCPlaylistCommon
111 o_outline_dict = [[NSMutableDictionary alloc] init];
117 playlist_t * p_playlist = pl_Yield( VLCIntf );
118 [o_outline_view setTarget: self];
119 [o_outline_view setDelegate: self];
120 [o_outline_view setDataSource: self];
122 vlc_object_release( p_playlist );
128 [[o_tc_name headerCell] setStringValue:_NS("Name")];
129 [[o_tc_author headerCell] setStringValue:_NS("Author")];
130 [[o_tc_duration headerCell] setStringValue:_NS("Duration")];
133 - (NSOutlineView *)outlineView
135 return o_outline_view;
138 - (playlist_item_t *)selectedPlaylistItem
140 return [[o_outline_view itemAtRow: [o_outline_view selectedRow]]
146 @implementation VLCPlaylistCommon (NSOutlineViewDataSource)
148 /* return the number of children for Obj-C pointer item */ /* DONE */
149 - (int)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
152 playlist_item_t *p_item = NULL;
153 playlist_t * p_playlist = pl_Yield( VLCIntf );
154 if( outlineView != o_outline_view )
156 vlc_object_release( p_playlist );
163 p_item = p_playlist->p_root_category;
167 p_item = (playlist_item_t *)[item pointerValue];
170 i_return = p_item->i_children;
171 vlc_object_release( p_playlist );
175 NSLog( @"%d children for %s", i_return, p_item->p_input->psz_name );
179 /* return the child at index for the Obj-C pointer item */ /* DONE */
180 - (id)outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item
182 playlist_item_t *p_return = NULL, *p_item = NULL;
184 playlist_t * p_playlist = pl_Yield( VLCIntf );
189 p_item = p_playlist->p_root_category;
193 p_item = (playlist_item_t *)[item pointerValue];
195 if( p_item && index < p_item->i_children && index >= 0 )
196 p_return = p_item->pp_children[index];
198 vlc_object_release( p_playlist );
200 o_value = [o_outline_dict objectForKey:[NSString stringWithFormat: @"%p", p_return]];
202 NSLog( @"%s", p_return->p_input->psz_name);
206 o_value = [[NSValue valueWithPointer: p_return] retain];
207 msg_Err( VLCIntf, "missing playlist item's pointer value" );
212 /* is the item expandable */
213 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
216 playlist_t *p_playlist = pl_Yield( VLCIntf );
221 if( p_playlist->p_root_category )
223 i_return = p_playlist->p_root_category->i_children;
228 playlist_item_t *p_item = (playlist_item_t *)[item pointerValue];
230 i_return = p_item->i_children;
232 vlc_object_release( p_playlist );
234 NSLog( @"expandable" );
241 /* retrieve the string values for the cells */
242 - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)o_tc byItem:(id)item
245 intf_thread_t *p_intf = VLCIntf;
246 playlist_item_t *p_item;
248 if( item == nil || ![item isKindOfClass: [NSValue class]] ) return( @"error" );
250 p_item = (playlist_item_t *)[item pointerValue];
255 //NSLog( @"values for %p", p_item );
257 if( [[o_tc identifier] isEqualToString:@"1"] )
259 o_value = [NSString stringWithUTF8String:
260 p_item->p_input->psz_name];
261 if( o_value == NULL )
262 o_value = [NSString stringWithCString:
263 p_item->p_input->psz_name];
265 else if( [[o_tc identifier] isEqualToString:@"2"] && p_item->p_input->p_meta &&
266 p_item->p_input->p_meta->psz_artist && *p_item->p_input->p_meta->psz_artist )
268 o_value = [NSString stringWithUTF8String:
269 p_item->p_input->p_meta->psz_artist];
270 if( o_value == NULL )
271 o_value = [NSString stringWithCString:
272 p_item->p_input->p_meta->psz_artist];
274 else if( [[o_tc identifier] isEqualToString:@"3"] )
276 char psz_duration[MSTRTIME_MAX_SIZE];
277 mtime_t dur = p_item->p_input->i_duration;
280 secstotimestr( psz_duration, dur/1000000 );
281 o_value = [NSString stringWithUTF8String: psz_duration];
285 o_value = @"-:--:--";
294 /*****************************************************************************
295 * VLCPlaylistWizard implementation
296 *****************************************************************************/
297 @implementation VLCPlaylistWizard
299 - (IBAction)reloadOutlineView
301 /* Only reload the outlineview if the wizard window is open since this can
302 be quite long on big playlists */
303 if( [[o_outline_view window] isVisible] )
305 [o_outline_view reloadData];
311 /*****************************************************************************
312 * extension to NSOutlineView's interface to fix compilation warnings
313 * and let us access these 2 functions properly
314 * this uses a private Apple-API, but works fine on all current OSX releases
315 * keep checking for compatiblity with future releases though
316 *****************************************************************************/
318 @interface NSOutlineView (UndocumentedSortImages)
319 + (NSImage *)_defaultTableHeaderSortImage;
320 + (NSImage *)_defaultTableHeaderReverseSortImage;
324 /*****************************************************************************
325 * VLCPlaylist implementation
326 *****************************************************************************/
327 @implementation VLCPlaylist
334 o_nodes_array = [[NSMutableArray alloc] init];
335 o_items_array = [[NSMutableArray alloc] init];
342 playlist_t * p_playlist = pl_Yield( VLCIntf );
343 vlc_list_t *p_list = vlc_list_find( p_playlist, VLC_OBJECT_MODULE,
348 [super awakeFromNib];
350 [o_outline_view setDoubleAction: @selector(playItem:)];
352 [o_outline_view registerForDraggedTypes:
353 [NSArray arrayWithObjects: NSFilenamesPboardType,
354 @"VLCPlaylistItemPboardType", nil]];
355 [o_outline_view setIntercellSpacing: NSMakeSize (0.0, 1.0)];
357 /* this uses private Apple API which works fine until 10.4,
358 * but keep checking in the future!
359 * These methods are being added artificially to NSOutlineView's interface above */
360 o_ascendingSortingImage = [[NSOutlineView class] _defaultTableHeaderSortImage];
361 o_descendingSortingImage = [[NSOutlineView class] _defaultTableHeaderReverseSortImage];
363 o_tc_sortColumn = nil;
365 for( i_index = 0; i_index < p_list->i_count; i_index++ )
368 module_t * p_parser = (module_t *)p_list->p_values[i_index].p_object ;
370 if( !strcmp( p_parser->psz_capability, "services_discovery" ) )
372 /* create the menu entries used in the playlist menu */
373 o_lmi = [[o_mi_services submenu] addItemWithTitle:
374 [NSString stringWithUTF8String:
375 p_parser->psz_longname ? p_parser->psz_longname :
376 ( p_parser->psz_shortname ? p_parser->psz_shortname:
377 p_parser->psz_object_name)]
378 action: @selector(servicesChange:)
380 [o_lmi setTarget: self];
381 [o_lmi setRepresentedObject:
382 [NSString stringWithCString: p_parser->psz_object_name]];
383 if( playlist_IsServicesDiscoveryLoaded( p_playlist,
384 p_parser->psz_object_name ) )
385 [o_lmi setState: NSOnState];
387 /* create the menu entries for the main menu */
388 o_lmi = [[o_mm_mi_services submenu] addItemWithTitle:
389 [NSString stringWithUTF8String:
390 p_parser->psz_longname ? p_parser->psz_longname :
391 ( p_parser->psz_shortname ? p_parser->psz_shortname:
392 p_parser->psz_object_name)]
393 action: @selector(servicesChange:)
395 [o_lmi setTarget: self];
396 [o_lmi setRepresentedObject:
397 [NSString stringWithCString: p_parser->psz_object_name]];
398 if( playlist_IsServicesDiscoveryLoaded( p_playlist,
399 p_parser->psz_object_name ) )
400 [o_lmi setState: NSOnState];
403 vlc_list_release( p_list );
404 vlc_object_release( p_playlist );
406 //[self playlistUpdated];
409 - (void)searchfieldChanged:(NSNotification *)o_notification
411 [o_search_field setStringValue:[[o_notification object] stringValue]];
418 [o_mi_save_playlist setTitle: _NS("Save Playlist...")];
419 [o_mi_play setTitle: _NS("Play")];
420 [o_mi_delete setTitle: _NS("Delete")];
421 [o_mi_recursive_expand setTitle: _NS("Expand Node")];
422 [o_mi_selectall setTitle: _NS("Select All")];
423 [o_mi_info setTitle: _NS("Information")];
424 [o_mi_preparse setTitle: _NS("Get Stream Information")];
425 [o_mi_sort_name setTitle: _NS("Sort Node by Name")];
426 [o_mi_sort_author setTitle: _NS("Sort Node by Author")];
427 [o_mi_services setTitle: _NS("Services discovery")];
428 [o_status_field setStringValue: [NSString stringWithFormat:
429 _NS("No items in the playlist")]];
432 [o_search_button setTitle: _NS("Search")];
434 [o_search_field setToolTip: _NS("Search in Playlist")];
435 [o_mi_addNode setTitle: _NS("Add Folder to Playlist")];
437 [o_save_accessory_text setStringValue: _NS("File Format:")];
438 [[o_save_accessory_popup itemAtIndex:0] setTitle: _NS("Extended M3U")];
439 [[o_save_accessory_popup itemAtIndex:1] setTitle: _NS("XML Shareable Playlist Format (XSPF)")];
442 - (void)playlistUpdated
446 /* Clear indications of any existing column sorting */
447 for( i = 0 ; i < [[o_outline_view tableColumns] count] ; i++ )
449 [o_outline_view setIndicatorImage:nil inTableColumn:
450 [[o_outline_view tableColumns] objectAtIndex:i]];
453 [o_outline_view setHighlightedTableColumn:nil];
454 o_tc_sortColumn = nil;
455 // TODO Find a way to keep the dict size to a minimum
456 //[o_outline_dict removeAllObjects];
457 [o_outline_view reloadData];
458 [[[[VLCMain sharedInstance] getWizard] getPlaylistWizard] reloadOutlineView];
459 [[[[VLCMain sharedInstance] getBookmarks] getDataTable] reloadData];
461 playlist_t *p_playlist = pl_Yield( VLCIntf );
463 if( p_playlist->i_size >= 2 )
465 [o_status_field setStringValue: [NSString stringWithFormat:
466 _NS("%i items in the playlist"), p_playlist->i_size]];
470 if( p_playlist->i_size == 0 )
472 [o_status_field setStringValue: _NS("No items in the playlist")];
476 [o_status_field setStringValue: _NS("1 item in the playlist")];
479 vlc_object_release( p_playlist );
482 - (void)playModeUpdated
484 playlist_t *p_playlist = pl_Yield( VLCIntf );
485 vlc_value_t val, val2;
487 var_Get( p_playlist, "loop", &val2 );
488 var_Get( p_playlist, "repeat", &val );
489 if( val.b_bool == VLC_TRUE )
491 [[[VLCMain sharedInstance] getControls] repeatOne];
493 else if( val2.b_bool == VLC_TRUE )
495 [[[VLCMain sharedInstance] getControls] repeatAll];
499 [[[VLCMain sharedInstance] getControls] repeatOff];
502 [[[VLCMain sharedInstance] getControls] shuffle];
504 vlc_object_release( p_playlist );
507 - (void)updateRowSelection
512 playlist_t *p_playlist = pl_Yield( VLCIntf );
513 playlist_item_t *p_item, *p_temp_item;
514 NSMutableArray *o_array = [NSMutableArray array];
516 p_item = p_playlist->status.p_item;
519 vlc_object_release(p_playlist);
523 p_temp_item = p_item;
524 while( p_temp_item->p_parent )
526 [o_array insertObject: [NSValue valueWithPointer: p_temp_item] atIndex: 0];
527 p_temp_item = p_temp_item->p_parent;
528 /*for (i = 0 ; i < p_temp_item->i_parents ; i++)
530 if( p_temp_item->pp_parents[i]->i_view == i_current_view )
532 p_temp_item = p_temp_item->pp_parents[i]->p_parent;
538 for( j = 0; j < [o_array count] - 1; j++ )
541 if( ( o_item = [o_outline_dict objectForKey:
542 [NSString stringWithFormat: @"%p",
543 [[o_array objectAtIndex:j] pointerValue]]] ) != nil )
545 [o_outline_view expandItem: o_item];
550 i_row = [o_outline_view rowForItem:[o_outline_dict
551 objectForKey:[NSString stringWithFormat: @"%p", p_item]]];
553 [o_outline_view selectRow: i_row byExtendingSelection: NO];
554 [o_outline_view scrollRowToVisible: i_row];
556 vlc_object_release( p_playlist );
558 /* update our info-panel to reflect the new item */
559 [[[VLCMain sharedInstance] getInfo] updatePanel];
562 /* Check if p_item is a child of p_node recursively. We need to check the item
563 existence first since OSX sometimes tries to redraw items that have been
564 deleted. We don't do it when not required since this verification takes
565 quite a long time on big playlists (yes, pretty hacky). */
566 - (BOOL)isItem: (playlist_item_t *)p_item
567 inNode: (playlist_item_t *)p_node
568 checkItemExistence:(BOOL)b_check
571 playlist_t * p_playlist = pl_Yield( VLCIntf );
572 playlist_item_t *p_temp_item = p_item;
574 if( p_node == p_item )
576 vlc_object_release(p_playlist);
580 if( p_node->i_children < 1)
582 vlc_object_release(p_playlist);
589 vlc_mutex_lock( &p_playlist->object_lock );
593 /* Since outlineView: willDisplayCell:... may call this function with
594 p_items that don't exist anymore, first check if the item is still
595 in the playlist. Any cleaner solution welcomed. */
596 for( i = 0; i < p_playlist->i_all_size; i++ )
598 if( p_playlist->pp_all_items[i] == p_item ) break;
599 else if ( i == p_playlist->i_all_size - 1 )
601 vlc_object_release( p_playlist );
602 vlc_mutex_unlock( &p_playlist->object_lock );
610 p_temp_item = p_temp_item->p_parent;
611 if( p_temp_item == p_node )
613 vlc_mutex_unlock( &p_playlist->object_lock );
614 vlc_object_release( p_playlist );
618 vlc_mutex_unlock( &p_playlist->object_lock );
621 vlc_object_release( p_playlist );
625 /* This method is usefull for instance to remove the selected children of an
626 already selected node */
627 - (void)removeItemsFrom:(id)o_items ifChildrenOf:(id)o_nodes
630 for( i = 0 ; i < [o_items count] ; i++ )
632 for ( j = 0 ; j < [o_nodes count] ; j++ )
634 if( o_items == o_nodes)
636 if( j == i ) continue;
638 if( [self isItem: [[o_items objectAtIndex:i] pointerValue]
639 inNode: [[o_nodes objectAtIndex:j] pointerValue]
640 checkItemExistence: NO] )
642 [o_items removeObjectAtIndex:i];
643 /* We need to execute the next iteration with the same index
644 since the current item has been deleted */
653 - (IBAction)savePlaylist:(id)sender
655 intf_thread_t * p_intf = VLCIntf;
656 playlist_t * p_playlist = pl_Yield( p_intf );
658 NSSavePanel *o_save_panel = [NSSavePanel savePanel];
659 NSString * o_name = [NSString stringWithFormat: @"%@", _NS("Untitled")];
661 //[o_save_panel setAllowedFileTypes: [NSArray arrayWithObjects: @"m3u", @"xpf", nil] ];
662 [o_save_panel setTitle: _NS("Save Playlist")];
663 [o_save_panel setPrompt: _NS("Save")];
664 [o_save_panel setAccessoryView: o_save_accessory_view];
666 if( [o_save_panel runModalForDirectory: nil
667 file: o_name] == NSOKButton )
669 NSString *o_filename = [o_save_panel filename];
671 if( [o_save_accessory_popup indexOfSelectedItem] == 1 )
673 NSString * o_real_filename;
675 range.location = [o_filename length] - [@".xspf" length];
676 range.length = [@".xspf" length];
678 if( [o_filename compare:@".xspf" options: NSCaseInsensitiveSearch
679 range: range] != NSOrderedSame )
681 o_real_filename = [NSString stringWithFormat: @"%@.xspf", o_filename];
685 o_real_filename = o_filename;
687 playlist_Export( p_playlist,
688 [o_real_filename fileSystemRepresentation],
689 p_playlist->p_local_category, "export-xspf" );
693 NSString * o_real_filename;
695 range.location = [o_filename length] - [@".m3u" length];
696 range.length = [@".m3u" length];
698 if( [o_filename compare:@".m3u" options: NSCaseInsensitiveSearch
699 range: range] != NSOrderedSame )
701 o_real_filename = [NSString stringWithFormat: @"%@.m3u", o_filename];
705 o_real_filename = o_filename;
707 playlist_Export( p_playlist,
708 [o_real_filename fileSystemRepresentation],
709 p_playlist->p_local_category, "export-m3u" );
712 vlc_object_release( p_playlist );
715 /* When called retrieves the selected outlineview row and plays that node or item */
716 - (IBAction)playItem:(id)sender
718 intf_thread_t * p_intf = VLCIntf;
719 playlist_t * p_playlist = pl_Yield( p_intf );
721 playlist_item_t *p_item;
722 playlist_item_t *p_node = NULL;
724 p_item = [[o_outline_view itemAtRow:[o_outline_view selectedRow]] pointerValue];
728 if( p_item->i_children == -1 )
730 p_node = p_item->p_parent;
736 if( p_node->i_children > 0 && p_node->pp_children[0]->i_children == -1 )
738 p_item = p_node->pp_children[0];
745 playlist_Control( p_playlist, PLAYLIST_VIEWPLAY, p_node, p_item );
747 vlc_object_release( p_playlist );
750 /* When called retrieves the selected outlineview row and plays that node or item */
751 - (IBAction)preparseItem:(id)sender
754 NSMutableArray *o_to_preparse;
755 intf_thread_t * p_intf = VLCIntf;
756 playlist_t * p_playlist = pl_Yield( p_intf );
758 o_to_preparse = [NSMutableArray arrayWithArray:[[o_outline_view selectedRowEnumerator] allObjects]];
759 i_count = [o_to_preparse count];
763 playlist_item_t *p_item = NULL;
765 for( i = 0; i < i_count; i++ )
767 o_number = [o_to_preparse lastObject];
768 i_row = [o_number intValue];
769 p_item = [[o_outline_view itemAtRow:i_row] pointerValue];
770 [o_to_preparse removeObject: o_number];
771 [o_outline_view deselectRow: i_row];
775 if( p_item->i_children == -1 )
777 playlist_PreparseEnqueue( p_playlist, p_item->p_input );
781 msg_Dbg( p_intf, "preparse of nodes not yet implemented" );
785 vlc_object_release( p_playlist );
786 [self playlistUpdated];
789 - (IBAction)servicesChange:(id)sender
791 NSMenuItem *o_mi = (NSMenuItem *)sender;
792 NSString *o_string = [o_mi representedObject];
793 playlist_t * p_playlist = pl_Yield( VLCIntf );
794 if( !playlist_IsServicesDiscoveryLoaded( p_playlist, [o_string cString] ) )
795 playlist_ServicesDiscoveryAdd( p_playlist, [o_string cString] );
797 playlist_ServicesDiscoveryRemove( p_playlist, [o_string cString] );
799 [o_mi setState: playlist_IsServicesDiscoveryLoaded( p_playlist,
800 [o_string cString] ) ? YES : NO];
802 vlc_object_release( p_playlist );
803 [self playlistUpdated];
807 - (IBAction)selectAll:(id)sender
809 [o_outline_view selectAll: nil];
812 - (IBAction)deleteItem:(id)sender
814 int i, i_count, i_row;
815 NSMutableArray *o_to_delete;
818 playlist_t * p_playlist;
819 intf_thread_t * p_intf = VLCIntf;
821 p_playlist = pl_Yield( p_intf );
823 o_to_delete = [NSMutableArray arrayWithArray:[[o_outline_view selectedRowEnumerator] allObjects]];
824 i_count = [o_to_delete count];
826 for( i = 0; i < i_count; i++ )
828 o_number = [o_to_delete lastObject];
829 i_row = [o_number intValue];
830 id o_item = [o_outline_view itemAtRow: i_row];
831 playlist_item_t *p_item = [o_item pointerValue];
832 [o_to_delete removeObject: o_number];
833 [o_outline_view deselectRow: i_row];
835 if( [[o_outline_view dataSource] outlineView:o_outline_view
836 numberOfChildrenOfItem: o_item] > 0 )
837 //is a node and not an item
839 if( p_playlist->status.i_status != PLAYLIST_STOPPED &&
840 [self isItem: p_playlist->status.p_item inNode:
841 ((playlist_item_t *)[o_item pointerValue])
842 checkItemExistence: NO] == YES )
844 // if current item is in selected node and is playing then stop playlist
845 playlist_Stop( p_playlist );
847 vlc_mutex_lock( &p_playlist->object_lock );
848 playlist_NodeDelete( p_playlist, p_item, VLC_TRUE, VLC_FALSE );
849 vlc_mutex_unlock( &p_playlist->object_lock );
853 playlist_LockDelete( p_playlist, p_item->i_id );
856 [self playlistUpdated];
857 vlc_object_release( p_playlist );
860 - (IBAction)sortNodeByName:(id)sender
862 [self sortNode: SORT_TITLE];
865 - (IBAction)sortNodeByAuthor:(id)sender
867 [self sortNode: SORT_ARTIST];
870 - (void)sortNode:(int)i_mode
872 playlist_t * p_playlist = pl_Yield( VLCIntf );
873 playlist_item_t * p_item;
875 if( [o_outline_view selectedRow] > -1 )
877 p_item = [[o_outline_view itemAtRow: [o_outline_view selectedRow]]
881 /*If no item is selected, sort the whole playlist*/
883 p_item = p_playlist->p_root_category;
886 if( p_item->i_children > -1 ) // the item is a node
888 vlc_mutex_lock( &p_playlist->object_lock );
889 playlist_RecursiveNodeSort( p_playlist, p_item, i_mode, ORDER_NORMAL );
890 vlc_mutex_unlock( &p_playlist->object_lock );
894 vlc_mutex_lock( &p_playlist->object_lock );
895 playlist_RecursiveNodeSort( p_playlist,
896 p_item->p_parent, i_mode, ORDER_NORMAL );
897 vlc_mutex_unlock( &p_playlist->object_lock );
899 vlc_object_release( p_playlist );
900 [self playlistUpdated];
903 - (input_item_t *)createItem:(NSDictionary *)o_one_item
905 intf_thread_t * p_intf = VLCIntf;
906 playlist_t * p_playlist = pl_Yield( p_intf );
908 input_item_t *p_input;
910 BOOL b_rem = FALSE, b_dir = FALSE;
911 NSString *o_uri, *o_name;
916 o_uri = (NSString *)[o_one_item objectForKey: @"ITEM_URL"];
917 o_name = (NSString *)[o_one_item objectForKey: @"ITEM_NAME"];
918 o_options = (NSArray *)[o_one_item objectForKey: @"ITEM_OPTIONS"];
920 /* Find the name for a disc entry ( i know, can you believe the trouble?) */
921 if( ( !o_name || [o_name isEqualToString:@""] ) && [o_uri rangeOfString: @"/dev/"].location != NSNotFound )
923 int i_count, i_index;
924 struct statfs *mounts = NULL;
926 i_count = getmntinfo (&mounts, MNT_NOWAIT);
927 /* getmntinfo returns a pointer to static data. Do not free. */
928 for( i_index = 0 ; i_index < i_count; i_index++ )
930 NSMutableString *o_temp, *o_temp2;
931 o_temp = [NSMutableString stringWithString: o_uri];
932 o_temp2 = [NSMutableString stringWithCString: mounts[i_index].f_mntfromname];
933 [o_temp replaceOccurrencesOfString: @"/dev/rdisk" withString: @"/dev/disk" options:nil range:NSMakeRange(0, [o_temp length]) ];
934 [o_temp2 replaceOccurrencesOfString: @"s0" withString: @"" options:nil range:NSMakeRange(0, [o_temp2 length]) ];
935 [o_temp2 replaceOccurrencesOfString: @"s1" withString: @"" options:nil range:NSMakeRange(0, [o_temp2 length]) ];
937 if( strstr( [o_temp fileSystemRepresentation], [o_temp2 fileSystemRepresentation] ) != NULL )
939 o_name = [[NSFileManager defaultManager] displayNameAtPath: [NSString stringWithCString:mounts[i_index].f_mntonname]];
943 /* If no name, then make a guess */
944 if( !o_name) o_name = [[NSFileManager defaultManager] displayNameAtPath: o_uri];
946 if( [[NSFileManager defaultManager] fileExistsAtPath:o_uri isDirectory:&b_dir] && b_dir &&
947 [[NSWorkspace sharedWorkspace] getFileSystemInfoForPath: o_uri isRemovable: &b_rem
948 isWritable:NULL isUnmountable:NULL description:NULL type:NULL] && b_rem )
950 /* All of this is to make sure CD's play when you D&D them on VLC */
951 /* Converts mountpoint to a /dev file */
954 NSMutableString *o_temp;
956 buf = (struct statfs *) malloc (sizeof(struct statfs));
957 statfs( [o_uri fileSystemRepresentation], buf );
958 psz_dev = strdup(buf->f_mntfromname);
959 o_temp = [NSMutableString stringWithCString: psz_dev ];
960 [o_temp replaceOccurrencesOfString: @"/dev/disk" withString: @"/dev/rdisk" options:nil range:NSMakeRange(0, [o_temp length]) ];
961 [o_temp replaceOccurrencesOfString: @"s0" withString: @"" options:nil range:NSMakeRange(0, [o_temp length]) ];
962 [o_temp replaceOccurrencesOfString: @"s1" withString: @"" options:nil range:NSMakeRange(0, [o_temp length]) ];
966 p_input = input_ItemNew( p_playlist, [o_uri fileSystemRepresentation], [o_name UTF8String] );
972 for( i = 0; i < (int)[o_options count]; i++ )
974 vlc_input_item_AddOption( p_input, strdup( [[o_options objectAtIndex:i] UTF8String] ) );
978 /* Recent documents menu */
979 o_true_file = [NSURL fileURLWithPath: o_uri];
980 if( o_true_file != nil )
982 [[NSDocumentController sharedDocumentController]
983 noteNewRecentDocumentURL: o_true_file];
986 vlc_object_release( p_playlist );
990 - (void)appendArray:(NSArray*)o_array atPos:(int)i_position enqueue:(BOOL)b_enqueue
993 playlist_t * p_playlist = pl_Yield( VLCIntf );
995 for( i_item = 0; i_item < (int)[o_array count]; i_item++ )
997 input_item_t *p_input;
998 NSDictionary *o_one_item;
1001 o_one_item = [o_array objectAtIndex: i_item];
1002 p_input = [self createItem: o_one_item];
1009 playlist_PlaylistAddInput( p_playlist, p_input, PLAYLIST_INSERT,
1010 i_position == -1 ? PLAYLIST_END : i_position + i_item );
1012 if( i_item == 0 && !b_enqueue )
1014 playlist_item_t *p_item;
1015 p_item = playlist_ItemGetByInput( p_playlist, p_input );
1016 playlist_Control( p_playlist, PLAYLIST_VIEWPLAY, NULL, p_item );
1020 playlist_item_t *p_item;
1021 p_item = playlist_ItemGetByInput( p_playlist, p_input );
1022 playlist_Control( p_playlist, PLAYLIST_PREPARSE, p_item );
1025 [self playlistUpdated];
1026 vlc_object_release( p_playlist );
1029 - (void)appendNodeArray:(NSArray*)o_array inNode:(playlist_item_t *)p_node atPos:(int)i_position enqueue:(BOOL)b_enqueue
1032 playlist_t * p_playlist = pl_Yield( VLCIntf );
1034 for( i_item = 0; i_item < (int)[o_array count]; i_item++ )
1036 input_item_t *p_input;
1037 NSDictionary *o_one_item;
1040 o_one_item = [o_array objectAtIndex: i_item];
1041 p_input = [self createItem: o_one_item];
1048 playlist_NodeAddInput( p_playlist, p_input, p_node,
1051 PLAYLIST_END : i_position + i_item );
1054 if( i_item == 0 && !b_enqueue )
1056 playlist_item_t *p_item;
1057 p_item = playlist_ItemGetByInput( p_playlist, p_input );
1058 playlist_Control( p_playlist, PLAYLIST_VIEWPLAY, NULL, p_item );
1062 playlist_item_t *p_item;
1063 p_item = playlist_ItemGetByInput( p_playlist, p_input );
1064 playlist_Control( p_playlist, PLAYLIST_PREPARSE, p_item );
1067 [self playlistUpdated];
1068 vlc_object_release( p_playlist );
1071 - (NSMutableArray *)subSearchItem:(playlist_item_t *)p_item
1073 playlist_t *p_playlist = pl_Yield( VLCIntf );
1074 playlist_item_t *p_selected_item;
1075 int i_current, i_selected_row;
1077 i_selected_row = [o_outline_view selectedRow];
1078 if (i_selected_row < 0)
1081 p_selected_item = (playlist_item_t *)[[o_outline_view itemAtRow:
1082 i_selected_row] pointerValue];
1084 for( i_current = 0; i_current < p_item->i_children ; i_current++ )
1087 NSString *o_current_name, *o_current_author;
1089 vlc_mutex_lock( &p_playlist->object_lock );
1090 o_current_name = [NSString stringWithUTF8String:
1091 p_item->pp_children[i_current]->p_input->psz_name];
1092 psz_temp = vlc_input_item_GetInfo( p_item->p_input ,
1093 _("Meta-information"),_("Artist") );
1094 o_current_author = [NSString stringWithUTF8String: psz_temp];
1096 vlc_mutex_unlock( &p_playlist->object_lock );
1098 if( p_selected_item == p_item->pp_children[i_current] &&
1099 b_selected_item_met == NO )
1101 b_selected_item_met = YES;
1103 else if( p_selected_item == p_item->pp_children[i_current] &&
1104 b_selected_item_met == YES )
1106 vlc_object_release( p_playlist );
1109 else if( b_selected_item_met == YES &&
1110 ( [o_current_name rangeOfString:[o_search_field
1111 stringValue] options:NSCaseInsensitiveSearch ].length ||
1112 [o_current_author rangeOfString:[o_search_field
1113 stringValue] options:NSCaseInsensitiveSearch ].length ) )
1115 vlc_object_release( p_playlist );
1116 /*Adds the parent items in the result array as well, so that we can
1118 return [NSMutableArray arrayWithObject: [NSValue
1119 valueWithPointer: p_item->pp_children[i_current]]];
1121 if( p_item->pp_children[i_current]->i_children > 0 )
1123 id o_result = [self subSearchItem:
1124 p_item->pp_children[i_current]];
1125 if( o_result != NULL )
1127 vlc_object_release( p_playlist );
1128 [o_result insertObject: [NSValue valueWithPointer:
1129 p_item->pp_children[i_current]] atIndex:0];
1134 vlc_object_release( p_playlist );
1138 - (IBAction)searchItem:(id)sender
1140 playlist_t * p_playlist = pl_Yield( VLCIntf );
1146 b_selected_item_met = NO;
1148 /*First, only search after the selected item:*
1149 *(b_selected_item_met = NO) */
1150 o_result = [self subSearchItem:p_playlist->p_root_category];
1151 if( o_result == NULL )
1153 /* If the first search failed, search again from the beginning */
1154 o_result = [self subSearchItem:p_playlist->p_root_category];
1156 if( o_result != NULL )
1159 if( [[o_result objectAtIndex: 0] pointerValue] ==
1160 p_playlist->p_local_category )
1165 for( i = i_start ; i < [o_result count] - 1 ; i++ )
1167 [o_outline_view expandItem: [o_outline_dict objectForKey:
1168 [NSString stringWithFormat: @"%p",
1169 [[o_result objectAtIndex: i] pointerValue]]]];
1171 i_row = [o_outline_view rowForItem: [o_outline_dict objectForKey:
1172 [NSString stringWithFormat: @"%p",
1173 [[o_result objectAtIndex: [o_result count] - 1 ]
1178 [o_outline_view selectRow:i_row byExtendingSelection: NO];
1179 [o_outline_view scrollRowToVisible: i_row];
1181 vlc_object_release( p_playlist );
1184 - (IBAction)recursiveExpandNode:(id)sender
1186 id o_item = [o_outline_view itemAtRow: [o_outline_view selectedRow]];
1187 playlist_item_t *p_item = (playlist_item_t *)[o_item pointerValue];
1189 if( ![[o_outline_view dataSource] outlineView: o_outline_view
1190 isItemExpandable: o_item] )
1192 o_item = [o_outline_dict objectForKey: [NSString
1193 stringWithFormat: @"%p", p_item->p_parent]];
1196 /* We need to collapse the node first, since OSX refuses to recursively
1197 expand an already expanded node, even if children nodes are collapsed. */
1198 [o_outline_view collapseItem: o_item collapseChildren: YES];
1199 [o_outline_view expandItem: o_item expandChildren: YES];
1202 - (NSMenu *)menuForEvent:(NSEvent *)o_event
1206 vlc_bool_t b_item_sel;
1208 pt = [o_outline_view convertPoint: [o_event locationInWindow]
1210 b_item_sel = ( [o_outline_view rowAtPoint: pt] != -1 &&
1211 [o_outline_view selectedRow] != -1 );
1212 b_rows = [o_outline_view numberOfRows] != 0;
1214 [o_mi_play setEnabled: b_item_sel];
1215 [o_mi_delete setEnabled: b_item_sel];
1216 [o_mi_selectall setEnabled: b_rows];
1217 [o_mi_info setEnabled: b_item_sel];
1218 [o_mi_preparse setEnabled: b_item_sel];
1219 [o_mi_recursive_expand setEnabled: b_item_sel];
1220 [o_mi_sort_name setEnabled: b_item_sel];
1221 [o_mi_sort_author setEnabled: b_item_sel];
1223 return( o_ctx_menu );
1226 - (void)outlineView: (NSTableView*)o_tv
1227 didClickTableColumn:(NSTableColumn *)o_tc
1229 int i_mode = 0, i_type;
1230 intf_thread_t *p_intf = VLCIntf;
1232 playlist_t *p_playlist = pl_Yield( p_intf );
1234 /* Check whether the selected table column header corresponds to a
1235 sortable table column*/
1236 if( !( o_tc == o_tc_name || o_tc == o_tc_author ) )
1238 vlc_object_release( p_playlist );
1242 if( o_tc_sortColumn == o_tc )
1244 b_isSortDescending = !b_isSortDescending;
1248 b_isSortDescending = VLC_FALSE;
1251 if( o_tc == o_tc_name )
1253 i_mode = SORT_TITLE;
1255 else if( o_tc == o_tc_author )
1257 i_mode = SORT_ARTIST;
1260 if( b_isSortDescending )
1262 i_type = ORDER_REVERSE;
1266 i_type = ORDER_NORMAL;
1269 vlc_mutex_lock( &p_playlist->object_lock );
1270 playlist_RecursiveNodeSort( p_playlist, p_playlist->p_root_category, i_mode, i_type );
1271 vlc_mutex_unlock( &p_playlist->object_lock );
1273 vlc_object_release( p_playlist );
1274 [self playlistUpdated];
1276 o_tc_sortColumn = o_tc;
1277 [o_outline_view setHighlightedTableColumn:o_tc];
1279 if( b_isSortDescending )
1281 [o_outline_view setIndicatorImage:o_descendingSortingImage
1282 inTableColumn:o_tc];
1286 [o_outline_view setIndicatorImage:o_ascendingSortingImage
1287 inTableColumn:o_tc];
1292 - (void)outlineView:(NSOutlineView *)outlineView
1293 willDisplayCell:(id)cell
1294 forTableColumn:(NSTableColumn *)tableColumn
1297 playlist_t *p_playlist = pl_Yield( VLCIntf );
1301 o_playing_item = [o_outline_dict objectForKey:
1302 [NSString stringWithFormat:@"%p", p_playlist->status.p_item]];
1304 if( [self isItem: [o_playing_item pointerValue] inNode:
1305 [item pointerValue] checkItemExistence: YES]
1306 || [o_playing_item isEqual: item] )
1308 [cell setFont: [NSFont boldSystemFontOfSize: 0]];
1312 [cell setFont: [NSFont systemFontOfSize: 0]];
1314 vlc_object_release( p_playlist );
1317 - (IBAction)addNode:(id)sender
1319 /* we have to create a new thread here because otherwise we would block the
1320 * interface since the interaction-stuff and this code would run in the same
1322 [NSThread detachNewThreadSelector: @selector(addNodeThreadedly)
1323 toTarget: self withObject:nil];
1324 [self playlistUpdated];
1327 - (void)addNodeThreadedly
1329 NSAutoreleasePool * ourPool = [[NSAutoreleasePool alloc] init];
1331 /* simply adds a new node to the end of the playlist */
1332 playlist_t * p_playlist = pl_Yield( VLCIntf );
1333 vlc_thread_set_priority( p_playlist, VLC_THREAD_PRIORITY_LOW );
1336 char *psz_name = NULL;
1337 playlist_item_t * p_item;
1338 ret_v = intf_UserStringInput( p_playlist, _("New Node"),
1339 _("Please enter a name for the new node."), &psz_name );
1340 if( psz_name != NULL && psz_name != "" )
1341 p_item = playlist_NodeCreate( p_playlist, psz_name,
1342 p_playlist->p_local_category );
1344 p_item = playlist_NodeCreate( p_playlist, _("Empty Folder"),
1345 p_playlist->p_local_category );
1348 msg_Warn( VLCIntf, "node creation failed" );
1350 vlc_object_release( p_playlist );
1356 @implementation VLCPlaylist (NSOutlineViewDataSource)
1358 - (id)outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item
1360 id o_value = [super outlineView: outlineView child: index ofItem: item];
1361 playlist_t *p_playlist = pl_Yield( VLCIntf );
1363 /* FIXME: playlist->i_size doesn't provide the correct number of items anymore
1364 * check the playlist API for the fixed function, once zorglub implemented it -- fpk, 9/17/06 */
1366 if( p_playlist->i_size >= 2 )
1368 [o_status_field setStringValue: [NSString stringWithFormat:
1369 _NS("%i items in the playlist"), p_playlist->i_size]];
1373 if( p_playlist->i_size == 0 )
1375 [o_status_field setStringValue: _NS("No items in the playlist")];
1379 [o_status_field setStringValue: _NS("1 item in the playlist")];
1382 vlc_object_release( p_playlist );
1384 [o_outline_dict setObject:o_value forKey:[NSString stringWithFormat:@"%p",
1385 [o_value pointerValue]]];
1386 msg_Dbg( VLCIntf, "adding item %p", [o_value pointerValue] );
1391 /* Required for drag & drop and reordering */
1392 - (BOOL)outlineView:(NSOutlineView *)outlineView writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pboard
1395 playlist_t *p_playlist = pl_Yield( VLCIntf );
1397 /* First remove the items that were moved during the last drag & drop
1399 [o_items_array removeAllObjects];
1400 [o_nodes_array removeAllObjects];
1402 for( i = 0 ; i < [items count] ; i++ )
1404 id o_item = [items objectAtIndex: i];
1406 /* Refuse to move items that are not in the General Node
1407 (Service Discovery) */
1408 if( ![self isItem: [o_item pointerValue] inNode:
1409 p_playlist->p_local_category checkItemExistence: NO])
1411 vlc_object_release(p_playlist);
1414 /* Fill the items and nodes to move in 2 different arrays */
1415 if( ((playlist_item_t *)[o_item pointerValue])->i_children > 0 )
1416 [o_nodes_array addObject: o_item];
1418 [o_items_array addObject: o_item];
1421 /* Now we need to check if there are selected items that are in already
1422 selected nodes. In that case, we only want to move the nodes */
1423 [self removeItemsFrom: o_nodes_array ifChildrenOf: o_nodes_array];
1424 [self removeItemsFrom: o_items_array ifChildrenOf: o_nodes_array];
1426 /* We add the "VLCPlaylistItemPboardType" type to be able to recognize
1427 a Drop operation coming from the playlist. */
1429 [pboard declareTypes: [NSArray arrayWithObjects:
1430 @"VLCPlaylistItemPboardType", nil] owner: self];
1431 [pboard setData:[NSData data] forType:@"VLCPlaylistItemPboardType"];
1433 vlc_object_release(p_playlist);
1437 - (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id <NSDraggingInfo>)info proposedItem:(id)item proposedChildIndex:(int)index
1439 playlist_t *p_playlist = pl_Yield( VLCIntf );
1440 NSPasteboard *o_pasteboard = [info draggingPasteboard];
1442 if( !p_playlist ) return NSDragOperationNone;
1444 /* Dropping ON items is not allowed if item is not a node */
1447 if( index == NSOutlineViewDropOnItemIndex &&
1448 ((playlist_item_t *)[item pointerValue])->i_children == -1 )
1450 vlc_object_release( p_playlist );
1451 return NSDragOperationNone;
1455 /* We refuse to drop an item in anything else than a child of the General
1456 Node. We still accept items that would be root nodes of the outlineview
1457 however, to allow drop in an empty playlist. */
1458 if( !([self isItem: [item pointerValue] inNode: p_playlist->p_local_category
1459 checkItemExistence: NO] || item == nil) )
1461 vlc_object_release( p_playlist );
1462 return NSDragOperationNone;
1465 /* Drop from the Playlist */
1466 if( [[o_pasteboard types] containsObject: @"VLCPlaylistItemPboardType"] )
1469 for( i = 0 ; i < [o_nodes_array count] ; i++ )
1471 /* We refuse to Drop in a child of an item we are moving */
1472 if( [self isItem: [item pointerValue] inNode:
1473 [[o_nodes_array objectAtIndex: i] pointerValue]
1474 checkItemExistence: NO] )
1476 vlc_object_release( p_playlist );
1477 return NSDragOperationNone;
1480 vlc_object_release( p_playlist );
1481 return NSDragOperationMove;
1484 /* Drop from the Finder */
1485 else if( [[o_pasteboard types] containsObject: NSFilenamesPboardType] )
1487 vlc_object_release( p_playlist );
1488 return NSDragOperationGeneric;
1490 vlc_object_release( p_playlist );
1491 return NSDragOperationNone;
1494 - (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id <NSDraggingInfo>)info item:(id)item childIndex:(int)index
1496 playlist_t * p_playlist = pl_Yield( VLCIntf );
1497 NSPasteboard *o_pasteboard = [info draggingPasteboard];
1499 /* Drag & Drop inside the playlist */
1500 if( [[o_pasteboard types] containsObject: @"VLCPlaylistItemPboardType"] )
1502 int i_row, i_removed_from_node = 0;
1504 playlist_item_t *p_new_parent, *p_item = NULL;
1505 NSArray *o_all_items = [o_nodes_array arrayByAddingObjectsFromArray:
1507 /* If the item is to be dropped as root item of the outline, make it a
1508 child of the General node.
1509 Else, choose the proposed parent as parent. */
1510 if( item == nil ) p_new_parent = p_playlist->p_local_category;
1511 else p_new_parent = [item pointerValue];
1513 /* Make sure the proposed parent is a node.
1514 (This should never be true) */
1515 if( p_new_parent->i_children < 0 )
1517 vlc_object_release( p_playlist );
1521 for( i = 0; i < [o_all_items count]; i++ )
1523 playlist_item_t *p_old_parent = NULL;
1524 int i_old_index = 0;
1526 p_item = [[o_all_items objectAtIndex:i] pointerValue];
1527 p_old_parent = p_item->p_parent;
1530 /* We may need the old index later */
1531 if( p_new_parent == p_old_parent )
1534 for( j = 0; j < p_old_parent->i_children; j++ )
1536 if( p_old_parent->pp_children[j] == p_item )
1544 vlc_mutex_lock( &p_playlist->object_lock );
1545 // Acually detach the item from the old position
1546 if( playlist_NodeRemoveItem( p_playlist, p_item, p_old_parent ) ==
1550 /* Calculate the new index */
1553 /* If we move the item in the same node, we need to take into
1554 account that one item will be deleted */
1557 if ((p_new_parent == p_old_parent &&
1558 i_old_index < index + (int)i) )
1560 i_removed_from_node++;
1562 i_new_index = index + i - i_removed_from_node;
1564 // Reattach the item to the new position
1565 playlist_NodeInsert( p_playlist, p_item, p_new_parent, i_new_index );
1567 vlc_mutex_unlock( &p_playlist->object_lock );
1569 [self playlistUpdated];
1570 i_row = [o_outline_view rowForItem:[o_outline_dict
1571 objectForKey:[NSString stringWithFormat: @"%p",
1572 [[o_all_items objectAtIndex: 0] pointerValue]]]];
1576 i_row = [o_outline_view rowForItem:[o_outline_dict
1577 objectForKey:[NSString stringWithFormat: @"%p", p_new_parent]]];
1580 [o_outline_view deselectAll: self];
1581 [o_outline_view selectRow: i_row byExtendingSelection: NO];
1582 [o_outline_view scrollRowToVisible: i_row];
1584 vlc_object_release( p_playlist );
1588 else if( [[o_pasteboard types] containsObject: NSFilenamesPboardType] )
1591 playlist_item_t *p_node = [item pointerValue];
1593 NSArray *o_array = [NSArray array];
1594 NSArray *o_values = [[o_pasteboard propertyListForType:
1595 NSFilenamesPboardType]
1596 sortedArrayUsingSelector:
1597 @selector(caseInsensitiveCompare:)];
1599 for( i = 0; i < (int)[o_values count]; i++)
1601 NSDictionary *o_dic;
1602 o_dic = [NSDictionary dictionaryWithObject:[o_values
1603 objectAtIndex:i] forKey:@"ITEM_URL"];
1604 o_array = [o_array arrayByAddingObject: o_dic];
1609 [self appendArray: o_array atPos: index enqueue: YES];
1611 /* This should never occur */
1612 else if( p_node->i_children == -1 )
1614 vlc_object_release( p_playlist );
1619 [self appendNodeArray: o_array inNode: p_node
1620 atPos: index enqueue: YES];
1622 vlc_object_release( p_playlist );
1625 vlc_object_release( p_playlist );
1629 /* Delegate method of NSWindow */
1630 /*- (void)windowWillClose:(NSNotification *)aNotification
1632 [o_btn_playlist setState: NSOffState];