1 /*****************************************************************************
2 * playlist.m: MacOS X interface module
3 *****************************************************************************
4 * Copyright (C) 2002-2014 VLC authors and VideoLAN
7 * Authors: Jon Lech Johansen <jon-vl@nanocrew.net>
8 * Derk-Jan Hartman <hartman at videola/n dot org>
9 * Benjamin Pracht <bigben at videolab dot org>
10 * Felix Paul Kühne <fkuehne at videolan dot org>
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
25 *****************************************************************************/
28 * add 'icons' for different types of nodes? (http://www.cocoadev.com/index.pl?IconAndTextInTableCell)
29 * reimplement enable/disable item
33 /*****************************************************************************
35 *****************************************************************************/
36 #include <stdlib.h> /* malloc(), free() */
37 #include <sys/param.h> /* for MAXPATHLEN */
40 #include <sys/mount.h>
42 #import "CompatibilityFixes.h"
47 #import "playlistinfo.h"
53 #import "CoreInteraction.h"
56 #import <vlc_interface.h>
59 /*****************************************************************************
60 * VLCPlaylistView implementation
61 *****************************************************************************/
62 @implementation VLCPlaylistView
64 - (NSMenu *)menuForEvent:(NSEvent *)o_event
66 return([(VLCPlaylist *)[self delegate] menuForEvent: o_event]);
69 - (void)keyDown:(NSEvent *)o_event
73 if ([[o_event characters] length])
74 key = [[o_event characters] characterAtIndex: 0];
77 case NSDeleteCharacter:
78 case NSDeleteFunctionKey:
79 case NSDeleteCharFunctionKey:
80 case NSBackspaceCharacter:
81 [(VLCPlaylist *)[self delegate] deleteItem:self];
84 case NSEnterCharacter:
85 case NSCarriageReturnCharacter:
86 [(VLCPlaylist *)[[VLCMain sharedInstance] playlist] playItem:nil];
90 [super keyDown: o_event];
95 - (BOOL)validateMenuItem:(NSMenuItem *)item
97 if (([self numberOfSelectedRows] >= 1 && [item action] == @selector(delete:)) || [item action] == @selector(selectAll:))
103 - (BOOL)acceptsFirstResponder
108 - (BOOL)becomeFirstResponder
110 [self setNeedsDisplay:YES];
114 - (BOOL)resignFirstResponder
116 [self setNeedsDisplay:YES];
120 - (IBAction)delete:(id)sender
122 [[[VLCMain sharedInstance] playlist] deleteItem: sender];
127 /*****************************************************************************
128 * VLCPlaylistWizard implementation
129 *****************************************************************************/
130 @implementation VLCPlaylistWizard
132 - (IBAction)reloadOutlineView
134 /* Only reload the outlineview if the wizard window is open since this can
135 be quite long on big playlists */
143 /*****************************************************************************
144 * An extension to NSOutlineView's interface to fix compilation warnings
145 * and let us access these 2 functions properly.
146 * This uses a private API, but works fine on all current OSX releases.
147 * Radar ID 11739459 request a public API for this. However, it is probably
148 * easier and faster to recreate similar looking bitmaps ourselves.
149 *****************************************************************************/
151 @interface NSOutlineView (UndocumentedSortImages)
152 + (NSImage *)_defaultTableHeaderSortImage;
153 + (NSImage *)_defaultTableHeaderReverseSortImage;
157 /*****************************************************************************
158 * VLCPlaylist implementation
159 *****************************************************************************/
160 @interface VLCPlaylist ()
162 playlist_item_t * p_current_root_item;
164 NSImage *o_descendingSortingImage;
165 NSImage *o_ascendingSortingImage;
167 BOOL b_selected_item_met;
168 BOOL b_isSortDescending;
170 NSUInteger retainedRowSelection;
172 BOOL b_playlistmenu_nib_loaded;
176 - (void)saveTableColumns;
179 @implementation VLCPlaylist
183 NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
184 NSMutableArray *o_columnArray = [[NSMutableArray alloc] init];
185 [o_columnArray addObject: [NSArray arrayWithObjects:TITLE_COLUMN, [NSNumber numberWithFloat:190.], nil]];
186 [o_columnArray addObject: [NSArray arrayWithObjects:ARTIST_COLUMN, [NSNumber numberWithFloat:95.], nil]];
187 [o_columnArray addObject: [NSArray arrayWithObjects:DURATION_COLUMN, [NSNumber numberWithFloat:95.], nil]];
189 NSDictionary *appDefaults = [NSDictionary dictionaryWithObjectsAndKeys:
190 [NSArray arrayWithArray:o_columnArray], @"PlaylistColumnSelection",
191 [NSArray array], @"recentlyPlayedMediaList",
192 [NSDictionary dictionary], @"recentlyPlayedMedia", nil];
194 [defaults registerDefaults:appDefaults];
195 [o_columnArray release];
198 - (playlist_item_t *)currentPlaylistRoot
201 playlist_t *p_playlist = pl_Get(VLCIntf);
202 return p_playlist->p_playing;
214 if (config_GetInt(VLCIntf, "macosx-large-text")) {
215 fontToUse = [NSFont systemFontOfSize:13.];
218 fontToUse = [NSFont systemFontOfSize:11.];
222 NSArray *columns = [o_outline_view tableColumns];
223 NSUInteger count = columns.count;
224 for (NSUInteger x = 0; x < count; x++)
225 [[[columns objectAtIndex:x] dataCell] setFont:fontToUse];
226 [o_outline_view setRowHeight:rowHeight];
235 playlist_t * p_playlist = pl_Get(VLCIntf);
236 p_current_root_item = p_playlist->p_local_category;
237 o_outline_dict = [[NSMutableDictionary alloc] init];
244 [o_outline_dict release];
253 playlist_t * p_playlist = pl_Get(VLCIntf);
254 [o_outline_view setTarget: self];
255 [o_outline_view setDelegate: self];
256 [o_outline_view setAllowsEmptySelection: NO];
257 [o_outline_view expandItem: [o_outline_view itemAtRow:0]];
262 o_model = [[PLModel alloc] initWithOutlineView:o_outline_view playlist:p_playlist rootItem:p_current_root_item playlistObject:self];
263 [o_outline_view setDataSource:o_model];
264 [o_outline_view reloadData];
266 [o_outline_view setDoubleAction: @selector(playItem:)];
268 [o_outline_view registerForDraggedTypes: [NSArray arrayWithObjects:NSFilenamesPboardType, @"VLCPlaylistItemPboardType", nil]];
269 [o_outline_view setIntercellSpacing: NSMakeSize (0.0, 1.0)];
271 /* This uses a private API, but works fine on all current OSX releases.
272 * Radar ID 11739459 request a public API for this. However, it is probably
273 * easier and faster to recreate similar looking bitmaps ourselves. */
274 o_ascendingSortingImage = [[NSOutlineView class] _defaultTableHeaderSortImage];
275 o_descendingSortingImage = [[NSOutlineView class] _defaultTableHeaderReverseSortImage];
277 o_tc_sortColumn = nil;
279 NSArray * o_columnArray = [[NSUserDefaults standardUserDefaults] arrayForKey:@"PlaylistColumnSelection"];
280 NSUInteger count = [o_columnArray count];
282 id o_menu = [[VLCMain sharedInstance] mainMenu];
285 NSMenu *o_context_menu = [o_menu setupPlaylistTableColumnsMenu];
286 [o_playlist_header setMenu: o_context_menu];
288 for (NSUInteger i = 0; i < count; i++) {
289 o_column = [[o_columnArray objectAtIndex:i] objectAtIndex:0];
290 if ([o_column isEqualToString:@"status"])
293 if(![o_menu setPlaylistColumnTableState: NSOnState forColumn: o_column])
296 [[o_outline_view tableColumnWithIdentifier: o_column] setWidth: [[[o_columnArray objectAtIndex:i] objectAtIndex:1] floatValue]];
299 [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(applicationWillTerminate:) name: NSApplicationWillTerminateNotification object: nil];
304 - (void)applicationWillTerminate:(NSNotification *)notification
306 /* 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 */
307 [self saveTableColumns];
312 [o_mi_play setTitle: _NS("Play")];
313 [o_mi_delete setTitle: _NS("Delete")];
314 [o_mi_recursive_expand setTitle: _NS("Expand Node")];
315 [o_mi_selectall setTitle: _NS("Select All")];
316 [o_mi_info setTitle: _NS("Media Information...")];
317 [o_mi_dl_cover_art setTitle: _NS("Download Cover Art")];
318 [o_mi_preparse setTitle: _NS("Fetch Meta Data")];
319 [o_mi_revealInFinder setTitle: _NS("Reveal in Finder")];
320 [o_mm_mi_revealInFinder setTitle: _NS("Reveal in Finder")];
321 [[o_mm_mi_revealInFinder menu] setAutoenablesItems: NO];
322 [o_mi_sort_name setTitle: _NS("Sort Node by Name")];
323 [o_mi_sort_author setTitle: _NS("Sort Node by Author")];
325 [o_search_field setToolTip: _NS("Search in Playlist")];
328 - (void)playlistUpdated
330 /* Clear indications of any existing column sorting */
331 NSUInteger count = [[o_outline_view tableColumns] count];
332 for (NSUInteger i = 0 ; i < count ; i++)
333 [o_outline_view setIndicatorImage:nil inTableColumn: [[o_outline_view tableColumns] objectAtIndex:i]];
335 [o_outline_view setHighlightedTableColumn:nil];
336 o_tc_sortColumn = nil;
337 // TODO Find a way to keep the dict size to a minimum
338 //[o_outline_dict removeAllObjects];
339 [o_outline_view reloadData];
340 [[[[VLCMain sharedInstance] wizard] playlistWizard] reloadOutlineView];
342 [o_outline_view selectRowIndexes:[NSIndexSet indexSetWithIndex:retainedRowSelection] byExtendingSelection:NO];
344 [self outlineViewSelectionDidChange: nil];
345 [[VLCMain sharedInstance] updateMainWindow];
348 - (void)outlineViewSelectionDidChange:(NSNotification *)notification
351 // playlist_item_t * p_item = [[o_outline_view itemAtRow:[o_outline_view selectedRow]] pointerValue];
354 // /* update the state of our Reveal-in-Finder menu items */
355 // NSMutableString *o_mrl;
356 // char *psz_uri = input_item_GetURI(p_item->p_input);
358 // [o_mi_revealInFinder setEnabled: NO];
359 // [o_mm_mi_revealInFinder setEnabled: NO];
361 // o_mrl = [NSMutableString stringWithUTF8String: psz_uri];
363 // /* perform some checks whether it is a file and if it is local at all... */
364 // NSRange prefix_range = [o_mrl rangeOfString: @"file:"];
365 // if (prefix_range.location != NSNotFound)
366 // [o_mrl deleteCharactersInRange: prefix_range];
368 // if ([o_mrl characterAtIndex:0] == '/') {
369 // [o_mi_revealInFinder setEnabled: YES];
370 // [o_mm_mi_revealInFinder setEnabled: YES];
375 // /* update our info-panel to reflect the new item */
376 // [[[VLCMain sharedInstance] info] updatePanelWithItem:p_item->p_input];
380 - (BOOL)isSelectionEmpty
382 return [o_outline_view selectedRow] == -1;
385 - (void)updateRowSelection
388 playlist_t *p_playlist = pl_Get(VLCIntf);
389 playlist_item_t *p_item, *p_temp_item;
390 NSMutableArray *o_array = [NSMutableArray array];
393 p_item = playlist_CurrentPlayingItem(p_playlist);
394 if (p_item == NULL) {
399 p_temp_item = p_item;
400 while(p_temp_item->p_parent) {
401 [o_array insertObject: [NSValue valueWithPointer: p_temp_item] atIndex: 0];
402 p_temp_item = p_temp_item->p_parent;
406 NSUInteger count = [o_array count];
407 for (NSUInteger j = 0; j < count - 1; j++) {
409 if ((o_item = [o_outline_dict objectForKey:
410 [NSString stringWithFormat: @"%p",
411 [[o_array objectAtIndex:j] pointerValue]]]) != nil) {
412 [o_outline_view expandItem: o_item];
416 id o_item = [o_outline_dict objectForKey:[NSString stringWithFormat: @"%p", p_item]];
417 NSInteger i_index = [o_outline_view rowForItem:o_item];
418 [o_outline_view selectRowIndexes:[NSIndexSet indexSetWithIndex:i_index] byExtendingSelection:NO];
419 [o_outline_view setNeedsDisplay:YES];
422 /* Check if p_item is a child of p_node recursively. We need to check the item
423 existence first since OSX sometimes tries to redraw items that have been
424 deleted. We don't do it when not required since this verification takes
425 quite a long time on big playlists (yes, pretty hacky). */
427 // todo remove useless parameters
428 - (BOOL)isItem: (PLItem *)p_item inNode: (PLItem *)p_node checkItemExistence:(BOOL)b_check locked:(BOOL)b_locked
430 PLItem *p_temp_item = p_item;
432 if ([p_node plItemId] == [p_item plItemId])
436 p_temp_item = [p_temp_item parent];
437 if ([p_temp_item plItemId] == [p_node plItemId]) {
445 - (IBAction)savePlaylist:(id)sender
447 playlist_t * p_playlist = pl_Get(VLCIntf);
449 NSSavePanel *o_save_panel = [NSSavePanel savePanel];
450 NSString * o_name = [NSString stringWithFormat: @"%@", _NS("Untitled")];
452 [NSBundle loadNibNamed:@"PlaylistAccessoryView" owner:self];
454 [o_save_accessory_text setStringValue: _NS("File Format:")];
455 [[o_save_accessory_popup itemAtIndex:0] setTitle: _NS("Extended M3U")];
456 [[o_save_accessory_popup itemAtIndex:1] setTitle: _NS("XML Shareable Playlist Format (XSPF)")];
457 [[o_save_accessory_popup itemAtIndex:2] setTitle: _NS("HTML playlist")];
459 [o_save_panel setTitle: _NS("Save Playlist")];
460 [o_save_panel setPrompt: _NS("Save")];
461 [o_save_panel setAccessoryView: o_save_accessory_view];
462 [o_save_panel setNameFieldStringValue: o_name];
464 if ([o_save_panel runModal] == NSFileHandlingPanelOKButton) {
465 NSString *o_filename = [[o_save_panel URL] path];
467 if ([o_save_accessory_popup indexOfSelectedItem] == 0) {
468 NSString * o_real_filename;
470 range.location = [o_filename length] - [@".m3u" length];
471 range.length = [@".m3u" length];
473 if ([o_filename compare:@".m3u" options: NSCaseInsensitiveSearch range: range] != NSOrderedSame)
474 o_real_filename = [NSString stringWithFormat: @"%@.m3u", o_filename];
476 o_real_filename = o_filename;
478 playlist_Export(p_playlist,
479 [o_real_filename fileSystemRepresentation],
480 p_playlist->p_local_category, "export-m3u");
481 } else if ([o_save_accessory_popup indexOfSelectedItem] == 1) {
482 NSString * o_real_filename;
484 range.location = [o_filename length] - [@".xspf" length];
485 range.length = [@".xspf" length];
487 if ([o_filename compare:@".xspf" options: NSCaseInsensitiveSearch range: range] != NSOrderedSame)
488 o_real_filename = [NSString stringWithFormat: @"%@.xspf", o_filename];
490 o_real_filename = o_filename;
492 playlist_Export(p_playlist,
493 [o_real_filename fileSystemRepresentation],
494 p_playlist->p_local_category, "export-xspf");
496 NSString * o_real_filename;
498 range.location = [o_filename length] - [@".html" length];
499 range.length = [@".html" length];
501 if ([o_filename compare:@".html" options: NSCaseInsensitiveSearch range: range] != NSOrderedSame)
502 o_real_filename = [NSString stringWithFormat: @"%@.html", o_filename];
504 o_real_filename = o_filename;
506 playlist_Export(p_playlist,
507 [o_real_filename fileSystemRepresentation],
508 p_playlist->p_local_category, "export-html");
513 /* When called retrieves the selected outlineview row and plays that node or item */
514 - (IBAction)playItem:(id)sender
516 intf_thread_t * p_intf = VLCIntf;
517 playlist_t * p_playlist = pl_Get(p_intf);
519 playlist_item_t *p_item;
520 playlist_item_t *p_node = NULL;
522 // ignore clicks on column header when handling double action
523 if (sender != nil && [o_outline_view clickedRow] == -1 && sender != o_mi_play)
527 PLItem *o_item = [o_outline_view itemAtRow:[o_outline_view selectedRow]];
528 p_item = playlist_ItemGetById(p_playlist, [o_item plItemId]);
531 if (p_item->i_children == -1) {
532 p_node = p_item->p_parent;
535 if (p_node->i_children > 0 && p_node->pp_children[0]->i_children == -1)
536 p_item = p_node->pp_children[0];
541 playlist_Control(p_playlist, PLAYLIST_VIEWPLAY, pl_Locked, p_node, p_item);
546 - (IBAction)revealItemInFinder:(id)sender
548 NSIndexSet * selectedRows = [o_outline_view selectedRowIndexes];
549 NSUInteger count = [selectedRows count];
550 NSUInteger indexes[count];
551 [selectedRows getIndexes:indexes maxCount:count inIndexRange:nil];
553 NSMutableString * o_mrl;
554 for (NSUInteger i = 0; i < count; i++) {
555 PLItem *o_item = [o_outline_view itemAtRow:indexes[i]];
557 char * psz_url = decode_URI(input_item_GetURI([o_item input]));
558 o_mrl = [[NSMutableString alloc] initWithString: [NSString stringWithUTF8String:psz_url ? psz_url : ""]];
562 /* perform some checks whether it is a file and if it is local at all... */
563 if ([o_mrl length] > 0) {
564 NSRange prefix_range = [o_mrl rangeOfString: @"file:"];
565 if (prefix_range.location != NSNotFound)
566 [o_mrl deleteCharactersInRange: prefix_range];
568 if ([o_mrl characterAtIndex:0] == '/')
569 [[NSWorkspace sharedWorkspace] selectFile: o_mrl inFileViewerRootedAtPath: o_mrl];
576 /* When called retrieves the selected outlineview row and plays that node or item */
577 - (IBAction)preparseItem:(id)sender
580 NSIndexSet *o_selected_indexes;
581 intf_thread_t * p_intf = VLCIntf;
582 playlist_t * p_playlist = pl_Get(p_intf);
583 playlist_item_t *p_item = NULL;
585 o_selected_indexes = [o_outline_view selectedRowIndexes];
586 i_count = [o_selected_indexes count];
588 NSUInteger indexes[i_count];
589 [o_selected_indexes getIndexes:indexes maxCount:i_count inIndexRange:nil];
590 for (int i = 0; i < i_count; i++) {
591 PLItem *o_item = [o_outline_view itemAtRow:indexes[i]];
592 [o_outline_view deselectRow: indexes[i]];
594 if (![o_item isLeaf]) {
595 msg_Dbg(p_intf, "preparsing nodes not implemented");
599 libvlc_MetaRequest(p_intf->p_libvlc, [o_item input], META_REQUEST_OPTION_NONE);
602 [self playlistUpdated];
605 - (IBAction)downloadCoverArt:(id)sender
608 NSIndexSet *o_selected_indexes;
609 intf_thread_t * p_intf = VLCIntf;
610 playlist_t * p_playlist = pl_Get(p_intf);
611 playlist_item_t *p_item = NULL;
613 o_selected_indexes = [o_outline_view selectedRowIndexes];
614 i_count = [o_selected_indexes count];
616 NSUInteger indexes[i_count];
617 [o_selected_indexes getIndexes:indexes maxCount:i_count inIndexRange:nil];
618 for (int i = 0; i < i_count; i++) {
619 PLItem *o_item = [o_outline_view itemAtRow: indexes[i]];
621 if (![o_item isLeaf])
624 libvlc_ArtRequest(p_intf->p_libvlc, [o_item input], META_REQUEST_OPTION_NONE);
626 [self playlistUpdated];
629 - (IBAction)selectAll:(id)sender
631 [o_outline_view selectAll: nil];
634 - (IBAction)showInfoPanel:(id)sender
636 [[[VLCMain sharedInstance] info] initPanel];
639 - (IBAction)deleteItem:(id)sender
642 NSIndexSet *o_selected_indexes;
643 intf_thread_t * p_intf = VLCIntf;
644 playlist_t * p_playlist = pl_Get(p_intf);
646 // check if deletion is allowed
647 if (![[self model] editAllowed])
650 o_selected_indexes = [o_outline_view selectedRowIndexes];
651 i_count = [o_selected_indexes count];
652 retainedRowSelection = [o_selected_indexes firstIndex];
653 if (retainedRowSelection == NSNotFound)
654 retainedRowSelection = 0;
657 NSUInteger indexes[i_count];
658 // if (i_count == [o_outline_view numberOfRows]) {
660 // playlist_NodeDelete(p_playlist, [self currentPlaylistRoot], true, false);
662 // [self playlistUpdated];
665 [o_selected_indexes getIndexes:indexes maxCount:i_count inIndexRange:nil];
666 for (int i = 0; i < i_count; i++) {
667 PLItem *o_item = [o_outline_view itemAtRow: indexes[i]];
668 [o_outline_view deselectRow: indexes[i]];
671 // if (p_item->i_children != -1) {
672 // //is a node and not an item
673 // if (playlist_Status(p_playlist) != PLAYLIST_STOPPED &&
674 // [self isItem: playlist_CurrentPlayingItem(p_playlist) inNode: ((playlist_item_t *)[o_item pointerValue])
675 // checkItemExistence: NO locked:YES] == YES)
676 // // if current item is in selected node and is playing then stop playlist
677 // playlist_Control(p_playlist, PLAYLIST_STOP, pl_Locked);
679 // playlist_NodeDelete(p_playlist, p_item, true, false);
682 playlist_DeleteFromInput(p_playlist, [o_item input], pl_Unlocked);
683 // [[o_item parent] deleteChild:o_item];
685 // [o_outline_view reloadData];
688 // [self playlistUpdated];
691 - (IBAction)sortNodeByName:(id)sender
693 [self sortNode: SORT_TITLE];
696 - (IBAction)sortNodeByAuthor:(id)sender
698 [self sortNode: SORT_ARTIST];
701 - (void)sortNode:(int)i_mode
703 playlist_t * p_playlist = pl_Get(VLCIntf);
704 playlist_item_t * p_item;
706 // TODO why do we need this kind of sort? It looks crap and confusing...
708 // if ([o_outline_view selectedRow] > -1) {
709 // p_item = [[o_outline_view itemAtRow: [o_outline_view selectedRow]] pointerValue];
713 // p_item = [self currentPlaylistRoot]; // If no item is selected, sort the whole playlist
716 // if (p_item->i_children > -1) // the item is a node
717 // playlist_RecursiveNodeSort(p_playlist, p_item, i_mode, ORDER_NORMAL);
719 // playlist_RecursiveNodeSort(p_playlist, p_item->p_parent, i_mode, ORDER_NORMAL);
722 // [self playlistUpdated];
725 - (input_item_t *)createItem:(NSDictionary *)o_one_item
727 intf_thread_t * p_intf = VLCIntf;
728 playlist_t * p_playlist = pl_Get(p_intf);
730 input_item_t *p_input;
731 BOOL b_rem = FALSE, b_dir = FALSE, b_writable = FALSE;
732 NSString *o_uri, *o_name, *o_path;
738 o_uri = (NSString *)[o_one_item objectForKey: @"ITEM_URL"];
739 o_nsurl = [NSURL URLWithString: o_uri];
740 o_path = [o_nsurl path];
741 o_name = (NSString *)[o_one_item objectForKey: @"ITEM_NAME"];
742 o_options = (NSArray *)[o_one_item objectForKey: @"ITEM_OPTIONS"];
744 if ([[NSFileManager defaultManager] fileExistsAtPath:o_path isDirectory:&b_dir] && b_dir &&
745 [[NSWorkspace sharedWorkspace] getFileSystemInfoForPath:o_path isRemovable: &b_rem
746 isWritable:&b_writable isUnmountable:NULL description:NULL type:NULL] && b_rem && !b_writable && [o_nsurl isFileURL]) {
748 NSString *diskType = [VLCOpen getVolumeTypeFromMountPath: o_path];
749 msg_Dbg(p_intf, "detected optical media of type %s in the file input", [diskType UTF8String]);
751 if ([diskType isEqualToString: kVLCMediaDVD])
752 o_uri = [NSString stringWithFormat: @"dvdnav://%@", [VLCOpen getBSDNodeFromMountPath: o_path]];
753 else if ([diskType isEqualToString: kVLCMediaVideoTSFolder])
754 o_uri = [NSString stringWithFormat: @"dvdnav://%@", o_path];
755 else if ([diskType isEqualToString: kVLCMediaAudioCD])
756 o_uri = [NSString stringWithFormat: @"cdda://%@", [VLCOpen getBSDNodeFromMountPath: o_path]];
757 else if ([diskType isEqualToString: kVLCMediaVCD])
758 o_uri = [NSString stringWithFormat: @"vcd://%@#0:0", [VLCOpen getBSDNodeFromMountPath: o_path]];
759 else if ([diskType isEqualToString: kVLCMediaSVCD])
760 o_uri = [NSString stringWithFormat: @"vcd://%@@0:0", [VLCOpen getBSDNodeFromMountPath: o_path]];
761 else if ([diskType isEqualToString: kVLCMediaBD] || [diskType isEqualToString: kVLCMediaBDMVFolder])
762 o_uri = [NSString stringWithFormat: @"bluray://%@", o_path];
764 msg_Warn(VLCIntf, "unknown disk type, treating %s as regular input", [o_path UTF8String]);
766 p_input = input_item_New([o_uri UTF8String], [[[NSFileManager defaultManager] displayNameAtPath: o_path] UTF8String]);
769 p_input = input_item_New([o_uri fileSystemRepresentation], o_name ? [o_name UTF8String] : NULL);
775 NSUInteger count = [o_options count];
776 for (NSUInteger i = 0; i < count; i++)
777 input_item_AddOption(p_input, [[o_options objectAtIndex:i] UTF8String], VLC_INPUT_OPTION_TRUSTED);
780 /* Recent documents menu */
781 if (o_nsurl != nil && (BOOL)config_GetInt(p_playlist, "macosx-recentitems") == YES)
782 [[NSDocumentController sharedDocumentController] noteNewRecentDocumentURL: o_nsurl];
787 - (void)appendArray:(NSArray*)o_array atPos:(int)i_position enqueue:(BOOL)b_enqueue
789 playlist_t * p_playlist = pl_Get(VLCIntf);
790 NSUInteger count = [o_array count];
791 BOOL b_usingPlaylist = [[self model] currentRootType] == ROOT_TYPE_PLAYLIST;
794 for (NSUInteger i_item = 0; i_item < count; i_item++) {
795 input_item_t *p_input;
796 NSDictionary *o_one_item;
799 o_one_item = [o_array objectAtIndex:i_item];
800 p_input = [self createItem: o_one_item];
805 int returnValue = playlist_AddInput(p_playlist, p_input, PLAYLIST_INSERT, i_position == -1 ? PLAYLIST_END : i_position + i_item, b_usingPlaylist, pl_Locked);
806 if (returnValue != VLC_SUCCESS) {
807 vlc_gc_decref(p_input);
811 if (i_item == 0 && !b_enqueue) {
812 playlist_item_t *p_item = playlist_ItemGetByInput(p_playlist, p_input);
813 playlist_Control(p_playlist, PLAYLIST_VIEWPLAY, pl_Locked, p_item->p_parent, p_item);
816 vlc_gc_decref(p_input);
819 [self playlistUpdated];
822 - (void)appendNodeArray:(NSArray*)o_array inNode:(playlist_item_t *)p_node atPos:(int)i_position enqueue:(BOOL)b_enqueue
824 playlist_t * p_playlist = pl_Get(VLCIntf);
825 NSUInteger count = [o_array count];
827 for (NSUInteger i_item = 0; i_item < count; i_item++) {
828 input_item_t *p_input;
829 NSDictionary *o_one_item;
833 o_one_item = [o_array objectAtIndex:i_item];
834 p_input = [self createItem: o_one_item];
840 playlist_NodeAddInput(p_playlist, p_input, p_node,
843 PLAYLIST_END : i_position + i_item,
847 if (i_item == 0 && !b_enqueue) {
848 playlist_item_t *p_item;
849 p_item = playlist_ItemGetByInput(p_playlist, p_input);
850 playlist_Control(p_playlist, PLAYLIST_VIEWPLAY, pl_Locked, p_node, p_item);
853 vlc_gc_decref(p_input);
855 // [self playlistUpdated];
858 - (IBAction)searchItem:(id)sender
860 [[self model] searchUpdate:[o_search_field stringValue]];
863 - (IBAction)recursiveExpandNode:(id)sender
865 NSIndexSet * selectedRows = [o_outline_view selectedRowIndexes];
866 NSUInteger count = [selectedRows count];
867 NSUInteger indexes[count];
868 [selectedRows getIndexes:indexes maxCount:count inIndexRange:nil];
871 playlist_item_t *p_item;
872 for (NSUInteger i = 0; i < count; i++) {
873 o_item = [o_outline_view itemAtRow: indexes[i]];
874 p_item = (playlist_item_t *)[o_item pointerValue];
876 if (![[o_outline_view dataSource] outlineView: o_outline_view isItemExpandable: o_item])
877 o_item = [o_outline_dict objectForKey: [NSString stringWithFormat: @"%p", p_item->p_parent]];
879 /* We need to collapse the node first, since OSX refuses to recursively
880 expand an already expanded node, even if children nodes are collapsed. */
881 [o_outline_view collapseItem: o_item collapseChildren: YES];
882 [o_outline_view expandItem: o_item expandChildren: YES];
884 selectedRows = [o_outline_view selectedRowIndexes];
885 [selectedRows getIndexes:indexes maxCount:count inIndexRange:nil];
889 - (NSMenu *)menuForEvent:(NSEvent *)o_event
891 if (!b_playlistmenu_nib_loaded)
892 b_playlistmenu_nib_loaded = [NSBundle loadNibNamed:@"PlaylistMenu" owner:self];
898 pt = [o_outline_view convertPoint: [o_event locationInWindow] fromView: nil];
899 int row = [o_outline_view rowAtPoint:pt];
900 if (row != -1 && ![[o_outline_view selectedRowIndexes] containsIndex: row])
901 [o_outline_view selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO];
903 b_item_sel = (row != -1 && [o_outline_view selectedRow] != -1);
904 b_rows = [o_outline_view numberOfRows] != 0;
906 playlist_t *p_playlist = pl_Get(VLCIntf);
907 bool b_del_allowed = [[self model] editAllowed];
909 [o_mi_play setEnabled: b_item_sel];
910 [o_mi_delete setEnabled: b_item_sel && b_del_allowed];
911 [o_mi_selectall setEnabled: b_rows];
912 [o_mi_info setEnabled: b_item_sel];
913 [o_mi_preparse setEnabled: b_item_sel];
914 [o_mi_recursive_expand setEnabled: b_item_sel];
915 [o_mi_sort_name setEnabled: b_item_sel];
916 [o_mi_sort_author setEnabled: b_item_sel];
921 - (void)outlineView: (NSOutlineView *)o_tv didClickTableColumn:(NSTableColumn *)o_tc
923 int i_mode, i_type = 0;
924 intf_thread_t *p_intf = VLCIntf;
925 NSString * o_identifier = [o_tc identifier];
927 playlist_t *p_playlist = pl_Get(p_intf);
929 if (o_tc_sortColumn == o_tc)
930 b_isSortDescending = !b_isSortDescending;
932 b_isSortDescending = false;
934 if (b_isSortDescending)
935 i_type = ORDER_REVERSE;
937 i_type = ORDER_NORMAL;
939 [[self model] sortForColumn:o_identifier withMode:i_type];
941 // TODO rework, why do we need a full call here?
942 // [self playlistUpdated];
944 /* Clear indications of any existing column sorting */
945 NSUInteger count = [[o_outline_view tableColumns] count];
946 for (NSUInteger i = 0 ; i < count ; i++)
947 [o_outline_view setIndicatorImage:nil inTableColumn: [[o_outline_view tableColumns] objectAtIndex:i]];
949 [o_outline_view setHighlightedTableColumn:nil];
950 o_tc_sortColumn = nil;
953 o_tc_sortColumn = o_tc;
954 [o_outline_view setHighlightedTableColumn:o_tc];
956 if (b_isSortDescending)
957 [o_outline_view setIndicatorImage:o_descendingSortingImage inTableColumn:o_tc];
959 [o_outline_view setIndicatorImage:o_ascendingSortingImage inTableColumn:o_tc];
963 - (void)outlineView:(NSOutlineView *)outlineView
964 willDisplayCell:(id)cell
965 forTableColumn:(NSTableColumn *)tableColumn
968 /* this method can be called when VLC is already dead, hence the extra checks */
969 intf_thread_t * p_intf = VLCIntf;
972 playlist_t *p_playlist = pl_Get(p_intf);
977 o_playing_item = [o_outline_dict objectForKey: [NSString stringWithFormat:@"%p", playlist_CurrentPlayingItem(p_playlist)]];
981 if (config_GetInt(VLCIntf, "macosx-large-text"))
982 fontToUse = [NSFont systemFontOfSize:13.];
984 fontToUse = [NSFont systemFontOfSize:11.];
986 BOOL b_is_playing = NO;
988 playlist_item_t *p_current_item = playlist_CurrentPlayingItem(p_playlist);
989 if (p_current_item) {
990 b_is_playing = p_current_item->i_id == [item plItemId];
995 TODO: repaint all items bold:
996 [self isItem: [o_playing_item pointerValue] inNode: [item pointerValue] checkItemExistence:YES locked:NO]
997 || [o_playing_item isEqual: item]
1001 [cell setFont: [[NSFontManager sharedFontManager] convertFont:fontToUse toHaveTrait:NSBoldFontMask]];
1003 [cell setFont: [[NSFontManager sharedFontManager] convertFont:fontToUse toNotHaveTrait:NSBoldFontMask]];
1008 playlist_t *p_playlist = pl_Get(VLCIntf);
1013 o_playing_item = [o_outline_dict objectForKey: [NSString stringWithFormat:@"%p", playlist_CurrentPlayingItem(p_playlist)]];
1016 return o_playing_item;
1019 // TODO remove method
1020 - (NSArray *)draggedItems
1022 return [[self model] draggedItems];
1025 - (void)setColumn: (NSString *)o_column state: (NSInteger)i_state translationDict:(NSDictionary *)o_dict
1027 NSTableColumn * o_work_tc;
1029 if (i_state == NSOnState) {
1030 NSString *o_title = [o_dict objectForKey:o_column];
1034 o_work_tc = [[NSTableColumn alloc] initWithIdentifier: o_column];
1035 [o_work_tc setEditable: NO];
1036 [[o_work_tc dataCell] setFont: [NSFont controlContentFontOfSize:11.]];
1038 [[o_work_tc headerCell] setStringValue: [o_dict objectForKey:o_column]];
1040 if ([o_column isEqualToString: TRACKNUM_COLUMN]) {
1041 [o_work_tc setWidth: 20.];
1042 [o_work_tc setResizingMask: NSTableColumnNoResizing];
1043 [[o_work_tc headerCell] setStringValue: @"#"];
1046 [o_outline_view addTableColumn: o_work_tc];
1047 [o_work_tc release];
1048 [o_outline_view reloadData];
1049 [o_outline_view setNeedsDisplay: YES];
1052 [o_outline_view removeTableColumn: [o_outline_view tableColumnWithIdentifier: o_column]];
1054 [o_outline_view setOutlineTableColumn: [o_outline_view tableColumnWithIdentifier:TITLE_COLUMN]];
1057 - (void)saveTableColumns
1059 NSMutableArray * o_arrayToSave = [[NSMutableArray alloc] init];
1060 NSArray * o_columns = [[NSArray alloc] initWithArray:[o_outline_view tableColumns]];
1061 NSUInteger count = [o_columns count];
1062 NSTableColumn * o_currentColumn;
1063 for (NSUInteger i = 0; i < count; i++) {
1064 o_currentColumn = [o_columns objectAtIndex:i];
1065 [o_arrayToSave addObject:[NSArray arrayWithObjects:[o_currentColumn identifier], [NSNumber numberWithFloat:[o_currentColumn width]], nil]];
1067 [[NSUserDefaults standardUserDefaults] setObject: o_arrayToSave forKey:@"PlaylistColumnSelection"];
1068 [[NSUserDefaults standardUserDefaults] synchronize];
1069 [o_columns release];
1070 [o_arrayToSave release];
1073 - (BOOL)isValidResumeItem:(input_item_t *)p_item
1075 char *psz_url = input_item_GetURI(p_item);
1076 NSString *o_url_string = toNSStr(psz_url);
1079 if ([o_url_string isEqualToString:@""])
1082 NSURL *o_url = [NSURL URLWithString:o_url_string];
1084 if (![o_url isFileURL])
1088 if (![[NSFileManager defaultManager] fileExistsAtPath:[o_url path] isDirectory:&isDir])
1097 - (void)updateAlertWindow:(NSTimer *)timer
1099 NSAlert *alert = [timer userInfo];
1101 --currentResumeTimeout;
1102 if (currentResumeTimeout <= 0) {
1103 [[alert window] close];
1107 NSString *buttonLabel = _NS("Restart playback");
1108 buttonLabel = [buttonLabel stringByAppendingFormat:@" (%d)", currentResumeTimeout];
1110 [[[alert buttons] objectAtIndex:2] setTitle:buttonLabel];
1113 - (void)continuePlaybackWhereYouLeftOff:(input_thread_t *)p_input_thread
1115 NSDictionary *recentlyPlayedFiles = [[NSUserDefaults standardUserDefaults] objectForKey:@"recentlyPlayedMedia"];
1116 if (!recentlyPlayedFiles)
1119 input_item_t *p_item = input_GetItem(p_input_thread);
1123 /* allow the user to over-write the start/stop/run-time */
1124 if (var_GetFloat(p_input_thread, "run-time") > 0 ||
1125 var_GetFloat(p_input_thread, "start-time") > 0 ||
1126 var_GetFloat(p_input_thread, "stop-time") > 0) {
1130 /* check for file existance before resuming */
1131 if (![self isValidResumeItem:p_item])
1134 char *psz_url = decode_URI(input_item_GetURI(p_item));
1137 NSString *url = toNSStr(psz_url);
1140 NSNumber *lastPosition = [recentlyPlayedFiles objectForKey:url];
1141 if (!lastPosition || lastPosition.intValue <= 0)
1144 int settingValue = config_GetInt(VLCIntf, "macosx-continue-playback");
1145 if (settingValue == 2) // never resume
1148 NSInteger returnValue = NSAlertErrorReturn;
1149 if (settingValue == 0) { // ask
1151 currentResumeTimeout = 6;
1152 NSString *o_restartButtonLabel = _NS("Restart playback");
1153 o_restartButtonLabel = [o_restartButtonLabel stringByAppendingFormat:@" (%d)", currentResumeTimeout];
1154 NSAlert *theAlert = [NSAlert alertWithMessageText:_NS("Continue playback?") defaultButton:_NS("Continue") alternateButton:o_restartButtonLabel otherButton:_NS("Always continue") informativeTextWithFormat:_NS("Playback of \"%@\" will continue at %@"), [NSString stringWithUTF8String:input_item_GetTitleFbName(p_item)], [[VLCStringUtility sharedInstance] stringForTime:lastPosition.intValue]];
1156 NSTimer *timer = [NSTimer timerWithTimeInterval:1
1158 selector:@selector(updateAlertWindow:)
1162 [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSModalPanelRunLoopMode];
1164 [[VLCCoreInteraction sharedInstance] pause];
1165 returnValue = [theAlert runModal];
1167 [[VLCCoreInteraction sharedInstance] playOrPause];
1169 // restart button was pressed or timeout happened
1170 if (returnValue == NSAlertAlternateReturn ||
1171 returnValue == NSRunAbortedResponse)
1175 mtime_t lastPos = (mtime_t)lastPosition.intValue * 1000000;
1176 msg_Dbg(VLCIntf, "continuing playback at %lld", lastPos);
1177 var_SetTime(p_input_thread, "time", lastPos);
1179 if (returnValue == NSAlertOtherReturn)
1180 config_PutInt(VLCIntf, "macosx-continue-playback", 1);
1183 - (void)storePlaybackPositionForItem:(input_thread_t *)p_input_thread
1185 if (!var_InheritBool(VLCIntf, "macosx-recentitems"))
1188 input_item_t *p_item = input_GetItem(p_input_thread);
1192 if (![self isValidResumeItem:p_item])
1195 char *psz_url = decode_URI(input_item_GetURI(p_item));
1198 NSString *url = toNSStr(psz_url);
1201 NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
1202 NSMutableDictionary *mutDict = [[NSMutableDictionary alloc] initWithDictionary:[defaults objectForKey:@"recentlyPlayedMedia"]];
1204 float relativePos = var_GetFloat(p_input_thread, "position");
1205 mtime_t pos = var_GetTime(p_input_thread, "time") / 1000000;
1206 mtime_t dur = input_item_GetDuration(p_item) / 1000000;
1208 NSMutableArray *mediaList = [[defaults objectForKey:@"recentlyPlayedMediaList"] mutableCopy];
1210 if (relativePos > .05 && relativePos < .95 && dur > 180) {
1211 [mutDict setObject:[NSNumber numberWithInt:pos] forKey:url];
1213 [mediaList removeObject:url];
1214 [mediaList addObject:url];
1215 NSUInteger mediaListCount = mediaList.count;
1216 if (mediaListCount > 30) {
1217 for (NSUInteger x = 0; x < mediaListCount - 30; x++) {
1218 [mutDict removeObjectForKey:[mediaList objectAtIndex:0]];
1219 [mediaList removeObjectAtIndex:0];
1223 [mutDict removeObjectForKey:url];
1224 [mediaList removeObject:url];
1226 [defaults setObject:mutDict forKey:@"recentlyPlayedMedia"];
1227 [defaults setObject:mediaList forKey:@"recentlyPlayedMediaList"];
1228 [defaults synchronize];
1231 [mediaList release];
1237 @implementation VLCPlaylist (NSOutlineViewDataSource)
1238 /* return the number of children for Obj-C pointer item */ /* DONE */
1239 - (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
1242 playlist_item_t *p_item = NULL;
1243 playlist_t * p_playlist = pl_Get(VLCIntf);
1244 //assert(outlineView == o_outline_view);
1248 p_item = p_current_root_item;
1250 p_item = (playlist_item_t *)[item pointerValue];
1253 i_return = p_item->i_children;
1256 return i_return > 0 ? i_return : 0;
1259 /* return the child at index for the Obj-C pointer item */ /* DONE */
1260 - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item
1262 playlist_item_t *p_return = NULL, *p_item = NULL;
1264 playlist_t * p_playlist = pl_Get(VLCIntf);
1268 p_item = p_current_root_item; /* root object */
1270 p_item = (playlist_item_t *)[item pointerValue];
1272 if (p_item && index < p_item->i_children && index >= 0)
1273 p_return = p_item->pp_children[index];
1276 o_value = [o_outline_dict objectForKey:[NSString stringWithFormat: @"%p", p_return]];
1278 if (o_value == nil) {
1279 /* FIXME: Why is there a warning if that happens all the time and seems
1280 * to be normal? Add an assert and fix it.
1281 * msg_Warn(VLCIntf, "playlist item misses pointer value, adding one"); */
1282 o_value = [[NSValue valueWithPointer: p_return] retain];
1285 [o_outline_dict setObject:o_value forKey:[NSString stringWithFormat:@"%p", [o_value pointerValue]]];
1290 /* is the item expandable */
1291 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
1294 playlist_t *p_playlist = pl_Get(VLCIntf);
1299 if (p_current_root_item) {
1300 i_return = p_current_root_item->i_children;
1303 playlist_item_t *p_item = (playlist_item_t *)[item pointerValue];
1305 i_return = p_item->i_children;
1309 return (i_return >= 0);
1312 /* retrieve the string values for the cells */
1313 - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)o_tc byItem:(id)item
1317 playlist_item_t *p_item;
1319 /* For error handling */
1320 static BOOL attempted_reload = NO;
1322 if (item == nil || ![item isKindOfClass: [NSValue class]]) {
1323 /* Attempt to fix the error by asking for a data redisplay
1324 * This might cause infinite loop, so add a small check */
1325 if (!attempted_reload) {
1326 attempted_reload = YES;
1327 [outlineView reloadData];
1332 p_item = (playlist_item_t *)[item pointerValue];
1333 if (!p_item || !p_item->p_input) {
1334 /* Attempt to fix the error by asking for a data redisplay
1335 * This might cause infinite loop, so add a small check */
1336 if (!attempted_reload) {
1337 attempted_reload = YES;
1338 [outlineView reloadData];
1343 attempted_reload = NO;
1344 NSString * o_identifier = [o_tc identifier];
1346 if ([o_identifier isEqualToString:TRACKNUM_COLUMN]) {
1347 psz_value = input_item_GetTrackNumber(p_item->p_input);
1349 o_value = [NSString stringWithUTF8String:psz_value];
1352 } else if ([o_identifier isEqualToString:TITLE_COLUMN]) {
1353 /* sanity check to prevent the NSString class from crashing */
1354 char *psz_title = input_item_GetTitleFbName(p_item->p_input);
1356 o_value = [NSString stringWithUTF8String:psz_title];
1359 } else if ([o_identifier isEqualToString:ARTIST_COLUMN]) {
1360 psz_value = input_item_GetArtist(p_item->p_input);
1362 o_value = [NSString stringWithUTF8String:psz_value];
1365 } else if ([o_identifier isEqualToString:@"duration"]) {
1366 char psz_duration[MSTRTIME_MAX_SIZE];
1367 mtime_t dur = input_item_GetDuration(p_item->p_input);
1369 secstotimestr(psz_duration, dur/1000000);
1370 o_value = [NSString stringWithUTF8String:psz_duration];
1374 } else if ([o_identifier isEqualToString:GENRE_COLUMN]) {
1375 psz_value = input_item_GetGenre(p_item->p_input);
1377 o_value = [NSString stringWithUTF8String:psz_value];
1380 } else if ([o_identifier isEqualToString:ALBUM_COLUMN]) {
1381 psz_value = input_item_GetAlbum(p_item->p_input);
1383 o_value = [NSString stringWithUTF8String:psz_value];
1386 } else if ([o_identifier isEqualToString:DESCRIPTION_COLUMN]) {
1387 psz_value = input_item_GetDescription(p_item->p_input);
1389 o_value = [NSString stringWithUTF8String:psz_value];
1392 } else if ([o_identifier isEqualToString:DATE_COLUMN]) {
1393 psz_value = input_item_GetDate(p_item->p_input);
1395 o_value = [NSString stringWithUTF8String:psz_value];
1398 } else if ([o_identifier isEqualToString:LANGUAGE_COLUMN]) {
1399 psz_value = input_item_GetLanguage(p_item->p_input);
1401 o_value = [NSString stringWithUTF8String:psz_value];
1405 else if ([o_identifier isEqualToString:URI_COLUMN]) {
1406 psz_value = decode_URI(input_item_GetURI(p_item->p_input));
1408 o_value = [NSString stringWithUTF8String:psz_value];
1412 else if ([o_identifier isEqualToString:FILESIZE_COLUMN]) {
1413 psz_value = input_item_GetURI(p_item->p_input);
1416 NSURL *url = [NSURL URLWithString:[NSString stringWithUTF8String:psz_value]];
1417 if ([url isFileURL]) {
1418 NSFileManager *fileManager = [NSFileManager defaultManager];
1419 if ([fileManager fileExistsAtPath:[url path]]) {
1421 NSDictionary *attributes = [fileManager attributesOfItemAtPath:[url path] error:&error];
1422 o_value = [VLCByteCountFormatter stringFromByteCount:[attributes fileSize] countStyle:NSByteCountFormatterCountStyleDecimal];
1428 else if ([o_identifier isEqualToString:@"status"]) {
1429 if (input_item_HasErrorWhenReading(p_item->p_input)) {
1430 o_value = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kAlertCautionIcon)];
1431 [o_value setSize: NSMakeSize(16,16)];