]> git.sesse.net Git - vlc/blob - modules/gui/macosx/playlist.m
* Fixed Meta for OSX, so i know what music i'm listening to.
[vlc] / modules / gui / macosx / playlist.m
1 /*****************************************************************************
2  * playlist.m: MacOS X interface module
3  *****************************************************************************
4 * Copyright (C) 2002-2006 the VideoLAN team
5  * $Id$
6  *
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  *
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.
15  *
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.
20  *
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  *****************************************************************************/
25
26 /* TODO
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
32  */
33
34
35 /*****************************************************************************
36  * Preamble
37  *****************************************************************************/
38 #include <stdlib.h>                                      /* malloc(), free() */
39 #include <sys/param.h>                                    /* for MAXPATHLEN */
40 #include <string.h>
41 #include <math.h>
42 #include <sys/mount.h>
43 #include <vlc_keys.h>
44
45 #include "intf.h"
46 #import "wizard.h"
47 #import "bookmarks.h"
48 #import "playlistinfo.h"
49 #include "playlist.h"
50 #include "controls.h"
51 #include "vlc_osd.h"
52 #include "misc.h"
53 #import <vlc_interaction.h>
54
55 /*****************************************************************************
56  * VLCPlaylistView implementation 
57  *****************************************************************************/
58 @implementation VLCPlaylistView
59
60 - (NSMenu *)menuForEvent:(NSEvent *)o_event
61 {
62     return( [[self delegate] menuForEvent: o_event] );
63 }
64
65 - (void)keyDown:(NSEvent *)o_event
66 {
67     unichar key = 0;
68
69     if( [[o_event characters] length] )
70     {
71         key = [[o_event characters] characterAtIndex: 0];
72     }
73
74     switch( key )
75     {
76         case NSDeleteCharacter:
77         case NSDeleteFunctionKey:
78         case NSDeleteCharFunctionKey:
79         case NSBackspaceCharacter:
80             [[self delegate] deleteItem:self];
81             break;
82
83         case NSEnterCharacter:
84         case NSCarriageReturnCharacter:
85             [(VLCPlaylist *)[[VLCMain sharedInstance] getPlaylist]
86                                                             playItem:self];
87             break;
88
89         default:
90             [super keyDown: o_event];
91             break;
92     }
93 }
94
95 @end
96
97
98 /*****************************************************************************
99  * VLCPlaylistCommon implementation
100  *
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
105
106 - (id)init
107 {
108     self = [super init];
109     if ( self != nil )
110     {
111         o_outline_dict = [[NSMutableDictionary alloc] init];
112     }
113     return self;
114 }
115 - (void)awakeFromNib
116 {
117     playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
118                                           FIND_ANYWHERE );
119     [o_outline_view setTarget: self];
120     [o_outline_view setDelegate: self];
121     [o_outline_view setDataSource: self];
122
123     vlc_object_release( p_playlist );
124     [self initStrings];
125 }
126
127 - (void)initStrings
128 {
129     [[o_tc_name headerCell] setStringValue:_NS("Name")];
130     [[o_tc_author headerCell] setStringValue:_NS("Author")];
131     [[o_tc_duration headerCell] setStringValue:_NS("Duration")];
132 }
133
134 - (NSOutlineView *)outlineView
135 {
136     return o_outline_view;
137 }
138
139 - (playlist_item_t *)selectedPlaylistItem
140 {
141     return [[o_outline_view itemAtRow: [o_outline_view selectedRow]]
142                                                                 pointerValue];
143 }
144
145 @end
146
147 @implementation VLCPlaylistCommon (NSOutlineViewDataSource)
148
149 /* return the number of children for Obj-C pointer item */ /* DONE */
150 - (int)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
151 {
152     int i_return = 0;
153     playlist_item_t *p_item = NULL;
154     playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
155                                                        FIND_ANYWHERE );
156     if( p_playlist == NULL )
157         return 0;
158     if( outlineView != o_outline_view )
159     {
160         vlc_object_release( p_playlist );
161         return 0;
162     }
163
164     if( item == nil )
165     {
166         /* root object */
167         p_item = p_playlist->p_root_category;
168     }
169     else
170     {
171         p_item = (playlist_item_t *)[item pointerValue];
172     }
173     if( p_item )
174             i_return = p_item->i_children;
175     vlc_object_release( p_playlist );
176
177     if( i_return <= 0 )
178         i_return = 0;
179 NSLog( @"%d children for %s", i_return, p_item->p_input->psz_name ); 
180     return i_return;
181 }
182
183 /* return the child at index for the Obj-C pointer item */ /* DONE */
184 - (id)outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item
185 {
186     playlist_item_t *p_return = NULL, *p_item = NULL;
187     NSValue *o_value;
188     playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
189                                                        FIND_ANYWHERE );
190
191     if( p_playlist == NULL )
192         return nil;
193
194     if( item == nil )
195     {
196         /* root object */
197         p_item = p_playlist->p_root_category;
198     }
199     else
200     {
201         p_item = (playlist_item_t *)[item pointerValue];      
202     }
203     if( p_item && index < p_item->i_children && index >= 0 )
204         p_return = p_item->pp_children[index];
205     
206     vlc_object_release( p_playlist );
207
208     o_value = [o_outline_dict objectForKey:[NSString stringWithFormat: @"%p", p_return]];
209     NSLog( @"%s", p_return->p_input->psz_name);
210     if( o_value == nil )
211     {
212         o_value = [[NSValue valueWithPointer: p_return] retain];
213         NSLog( @"error missing value" );
214     }
215     return o_value;
216 }
217
218 /* is the item expandable */
219 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
220 {
221     int i_return = 0;
222     playlist_t *p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
223                                                        FIND_ANYWHERE );
224     if( p_playlist == NULL )
225         return NO;
226
227     if( item == nil )
228     {
229         /* root object */
230         if( p_playlist->p_root_category )
231         {
232             i_return = p_playlist->p_root_category->i_children;
233         }
234     }
235     else
236     {
237         playlist_item_t *p_item = (playlist_item_t *)[item pointerValue];
238         if( p_item )
239             i_return = p_item->i_children;
240     }
241     vlc_object_release( p_playlist );
242
243 NSLog( @"expandable" ); 
244     if( i_return <= 0 )
245         return NO;
246     else
247         return YES;
248 }
249
250 /* retrieve the string values for the cells */
251 - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)o_tc byItem:(id)item
252 {
253     id o_value = nil;
254     intf_thread_t *p_intf = VLCIntf;
255     playlist_t *p_playlist;
256     playlist_item_t *p_item;
257     
258     if( item == nil || ![item isKindOfClass: [NSValue class]] ) return( @"error" );
259     
260     /* Check to see if the playlist is present */
261     p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST, FIND_ANYWHERE );
262     if( p_playlist == NULL ) return( @"error");
263     vlc_object_release( p_playlist);
264     
265     p_item = (playlist_item_t *)[item pointerValue];
266     if( p_item == NULL )
267     {
268         return( @"error");
269     }
270 NSLog( @"values for %p", p_item ); 
271     
272     if( [[o_tc identifier] isEqualToString:@"1"] )
273     {
274         o_value = [NSString stringWithUTF8String:
275             p_item->p_input->psz_name];
276         if( o_value == NULL )
277             o_value = [NSString stringWithCString:
278                 p_item->p_input->psz_name];
279     }
280     else if( [[o_tc identifier] isEqualToString:@"2"] && p_item->p_input->p_meta &&
281         p_item->p_input->p_meta->psz_artist && *p_item->p_input->p_meta->psz_artist )
282     {
283         o_value = [NSString stringWithUTF8String:
284             p_item->p_input->p_meta->psz_artist];
285         if( o_value == NULL )
286             o_value = [NSString stringWithCString:
287                 p_item->p_input->p_meta->psz_artist];
288     }
289     else if( [[o_tc identifier] isEqualToString:@"3"] )
290     {
291         char psz_duration[MSTRTIME_MAX_SIZE];
292         mtime_t dur = p_item->p_input->i_duration;
293         if( dur != -1 )
294         {
295             secstotimestr( psz_duration, dur/1000000 );
296             o_value = [NSString stringWithUTF8String: psz_duration];
297         }
298         else
299         {
300             o_value = @"-:--:--";
301         }
302     }
303
304     return( o_value );
305 }
306
307 @end
308
309 /*****************************************************************************
310  * VLCPlaylistWizard implementation
311  *****************************************************************************/
312 @implementation VLCPlaylistWizard
313
314 - (IBAction)reloadOutlineView
315 {
316     /* Only reload the outlineview if the wizard window is open since this can
317        be quite long on big playlists */
318     if( [[o_outline_view window] isVisible] )
319     {
320         [o_outline_view reloadData];
321     }
322 }
323
324 @end
325
326 /*****************************************************************************
327  * VLCPlaylist implementation
328  *****************************************************************************/
329 @implementation VLCPlaylist
330
331 - (id)init
332 {
333     self = [super init];
334     if ( self != nil )
335     {
336         o_nodes_array = [[NSMutableArray alloc] init];
337         o_items_array = [[NSMutableArray alloc] init];
338     }
339     return self;
340 }
341
342 - (void)awakeFromNib
343 {
344     playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
345                                           FIND_ANYWHERE );
346     vlc_list_t *p_list = vlc_list_find( p_playlist, VLC_OBJECT_MODULE,
347                                         FIND_ANYWHERE );
348
349     int i_index;
350
351     [super awakeFromNib];
352
353     [o_outline_view setDoubleAction: @selector(playItem:)];
354
355     [o_outline_view registerForDraggedTypes:
356         [NSArray arrayWithObjects: NSFilenamesPboardType,
357         @"VLCPlaylistItemPboardType", nil]];
358     [o_outline_view setIntercellSpacing: NSMakeSize (0.0, 1.0)];
359
360 /* We need to check whether _defaultTableHeaderSortImage exists, since it 
361 belongs to an Apple hidden private API, and then can "disapear" at any time*/
362
363     if( [[NSOutlineView class] respondsToSelector:@selector(_defaultTableHeaderSortImage)] )
364     {
365         o_ascendingSortingImage = [[NSOutlineView class] _defaultTableHeaderSortImage];
366     }
367     else
368     {
369         o_ascendingSortingImage = nil;
370     }
371
372     if( [[NSOutlineView class] respondsToSelector:@selector(_defaultTableHeaderReverseSortImage)] )
373     {
374         o_descendingSortingImage = [[NSOutlineView class] _defaultTableHeaderReverseSortImage];
375     }
376     else
377     {
378         o_descendingSortingImage = nil;
379     }
380
381     o_tc_sortColumn = nil;
382
383     for( i_index = 0; i_index < p_list->i_count; i_index++ )
384     {
385         NSMenuItem * o_lmi;
386         module_t * p_parser = (module_t *)p_list->p_values[i_index].p_object ;
387
388         if( !strcmp( p_parser->psz_capability, "services_discovery" ) )
389         {
390             /* create the menu entries used in the playlist menu */
391             o_lmi = [[o_mi_services submenu] addItemWithTitle:
392                      [NSString stringWithUTF8String:
393                      p_parser->psz_longname ? p_parser->psz_longname :
394                      ( p_parser->psz_shortname ? p_parser->psz_shortname:
395                      p_parser->psz_object_name)]
396                                              action: @selector(servicesChange:)
397                                              keyEquivalent: @""];
398             [o_lmi setTarget: self];
399             [o_lmi setRepresentedObject:
400                    [NSString stringWithCString: p_parser->psz_object_name]];
401             if( playlist_IsServicesDiscoveryLoaded( p_playlist,
402                     p_parser->psz_object_name ) )
403                 [o_lmi setState: NSOnState];
404                 
405             /* create the menu entries for the main menu */
406             o_lmi = [[o_mm_mi_services submenu] addItemWithTitle:
407                      [NSString stringWithUTF8String:
408                      p_parser->psz_longname ? p_parser->psz_longname :
409                      ( p_parser->psz_shortname ? p_parser->psz_shortname:
410                      p_parser->psz_object_name)]
411                                              action: @selector(servicesChange:)
412                                              keyEquivalent: @""];
413             [o_lmi setTarget: self];
414             [o_lmi setRepresentedObject:
415                    [NSString stringWithCString: p_parser->psz_object_name]];
416             if( playlist_IsServicesDiscoveryLoaded( p_playlist,
417                     p_parser->psz_object_name ) )
418                 [o_lmi setState: NSOnState];
419         }
420     }
421     vlc_list_release( p_list );
422     vlc_object_release( p_playlist );
423
424     //[self playlistUpdated];
425 }
426
427 - (void)searchfieldChanged:(NSNotification *)o_notification
428 {
429     [o_search_field setStringValue:[[o_notification object] stringValue]];
430 }
431
432 - (void)initStrings
433 {
434     [super initStrings];
435
436     [o_mi_save_playlist setTitle: _NS("Save Playlist...")];
437     [o_mi_play setTitle: _NS("Play")];
438     [o_mi_delete setTitle: _NS("Delete")];
439     [o_mi_recursive_expand setTitle: _NS("Expand Node")];
440     [o_mi_selectall setTitle: _NS("Select All")];
441     [o_mi_info setTitle: _NS("Information")];
442     [o_mi_preparse setTitle: _NS("Get Stream Information")];
443     [o_mi_sort_name setTitle: _NS("Sort Node by Name")];
444     [o_mi_sort_author setTitle: _NS("Sort Node by Author")];
445     [o_mi_services setTitle: _NS("Services discovery")];
446     [o_status_field setStringValue: [NSString stringWithFormat:
447                         _NS("No items in the playlist")]];
448
449     [o_random_ckb setTitle: _NS("Random")];
450 #if 0
451     [o_search_button setTitle: _NS("Search")];
452 #endif
453     [o_search_field setToolTip: _NS("Search in Playlist")];
454     [[o_loop_popup itemAtIndex:0] setTitle: _NS("Standard Play")];
455     [[o_loop_popup itemAtIndex:1] setTitle: _NS("Repeat One")];
456     [[o_loop_popup itemAtIndex:2] setTitle: _NS("Repeat All")];
457     [o_mi_addNode setTitle: _NS("Add Folder to Playlist")];
458
459     [o_save_accessory_text setStringValue: _NS("File Format:")];
460     [[o_save_accessory_popup itemAtIndex:0] setTitle: _NS("Extended M3U")];
461     [[o_save_accessory_popup itemAtIndex:1] setTitle: _NS("XML Shareable Playlist Format (XSPF)")];
462 }
463
464 - (void)playlistUpdated
465 {
466     unsigned int i;
467
468     /* Clear indications of any existing column sorting */
469     for( i = 0 ; i < [[o_outline_view tableColumns] count] ; i++ )
470     {
471         [o_outline_view setIndicatorImage:nil inTableColumn:
472                             [[o_outline_view tableColumns] objectAtIndex:i]];
473     }
474
475     [o_outline_view setHighlightedTableColumn:nil];
476     o_tc_sortColumn = nil;
477     // TODO Find a way to keep the dict size to a minimum
478     //[o_outline_dict removeAllObjects];
479     [o_outline_view reloadData];
480     [[[[VLCMain sharedInstance] getWizard] getPlaylistWizard] reloadOutlineView];
481     [[[[VLCMain sharedInstance] getBookmarks] getDataTable] reloadData];
482
483     playlist_t *p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
484                                           FIND_ANYWHERE );
485     if(! p_playlist )
486         return;
487
488     if( p_playlist->i_size >= 2 )
489     {
490         [o_status_field setStringValue: [NSString stringWithFormat:
491                     _NS("%i items in the playlist"), p_playlist->i_size]];
492     }
493     else
494     {
495         if( p_playlist->i_size == 0 )
496         {
497             [o_status_field setStringValue: _NS("No items in the playlist")];
498         }
499         else
500         {
501             [o_status_field setStringValue: _NS("1 item in the playlist")];
502         }
503     }
504     vlc_object_release( p_playlist );
505 }
506
507 - (void)playModeUpdated
508 {
509     playlist_t *p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
510                                           FIND_ANYWHERE );
511     vlc_value_t val, val2;
512
513     if( p_playlist == NULL )
514     {
515         return;
516     }
517
518     var_Get( p_playlist, "loop", &val2 );
519     var_Get( p_playlist, "repeat", &val );
520     if( val.b_bool == VLC_TRUE )
521     {
522         [o_loop_popup selectItemAtIndex: 1];
523    }
524     else if( val2.b_bool == VLC_TRUE )
525     {
526         [o_loop_popup selectItemAtIndex: 2];
527     }
528     else
529     {
530         [o_loop_popup selectItemAtIndex: 0];
531     }
532
533     var_Get( p_playlist, "random", &val );
534     [o_random_ckb setState: val.b_bool];
535
536     vlc_object_release( p_playlist );
537 }
538
539 - (void)updateRowSelection
540 {
541     int i_row;
542     unsigned int j;
543
544     playlist_t *p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
545                                           FIND_ANYWHERE );
546     playlist_item_t *p_item, *p_temp_item;
547     NSMutableArray *o_array = [NSMutableArray array];
548
549     if( p_playlist == NULL )
550         return;
551
552     p_item = p_playlist->status.p_item;
553     if( p_item == NULL )
554     {
555         vlc_object_release(p_playlist);
556         return;
557     }
558
559     p_temp_item = p_item;
560     while( p_temp_item->p_parent )
561     {
562         [o_array insertObject: [NSValue valueWithPointer: p_temp_item] atIndex: 0];
563         p_temp_item = p_temp_item->p_parent;
564         /*for (i = 0 ; i < p_temp_item->i_parents ; i++)
565         {
566             if( p_temp_item->pp_parents[i]->i_view == i_current_view )
567             {
568                 p_temp_item = p_temp_item->pp_parents[i]->p_parent;
569                 break;
570             }
571         }*/
572     }
573
574     for( j = 0; j < [o_array count] - 1; j++ )
575     {
576         id o_item;
577         if( ( o_item = [o_outline_dict objectForKey:
578                             [NSString stringWithFormat: @"%p",
579                             [[o_array objectAtIndex:j] pointerValue]]] ) != nil )
580         {
581             msg_Err( p_playlist, "o_item: %p", o_item );
582             [o_outline_view expandItem: o_item];
583         }
584
585     }
586
587     i_row = [o_outline_view rowForItem:[o_outline_dict
588             objectForKey:[NSString stringWithFormat: @"%p", p_item]]];
589
590     [o_outline_view selectRow: i_row byExtendingSelection: NO];
591     [o_outline_view scrollRowToVisible: i_row];
592
593     vlc_object_release(p_playlist);
594
595     /* update our info-panel to reflect the new item */
596     [[[VLCMain sharedInstance] getInfo] updatePanel];
597 }
598
599 /* Check if p_item is a child of p_node recursively. We need to check the item
600    existence first since OSX sometimes tries to redraw items that have been
601    deleted. We don't do it when not required  since this verification takes
602    quite a long time on big playlists (yes, pretty hacky). */
603 - (BOOL)isItem: (playlist_item_t *)p_item
604                     inNode: (playlist_item_t *)p_node
605                     checkItemExistence:(BOOL)b_check
606
607 {
608     playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
609                                           FIND_ANYWHERE );
610     playlist_item_t *p_temp_item = p_item;
611
612     if( p_playlist == NULL )
613     {
614         return NO;
615     }
616
617     if( p_node == p_item )
618     {
619         vlc_object_release(p_playlist);
620         return YES;
621     }
622
623     if( p_node->i_children < 1)
624     {
625         vlc_object_release(p_playlist);
626         return NO;
627     }
628
629     if ( p_temp_item )
630     {
631         int i;
632         vlc_mutex_lock( &p_playlist->object_lock );
633
634         if( b_check )
635         {
636         /* Since outlineView: willDisplayCell:... may call this function with
637            p_items that don't exist anymore, first check if the item is still
638            in the playlist. Any cleaner solution welcomed. */
639             for( i = 0; i < p_playlist->i_all_size; i++ )
640             {
641                 if( p_playlist->pp_all_items[i] == p_item ) break;
642                 else if ( i == p_playlist->i_all_size - 1 )
643                 {
644                     vlc_object_release( p_playlist );
645                     vlc_mutex_unlock( &p_playlist->object_lock );
646                     return NO;
647                 }
648             }
649         }
650
651         while( p_temp_item )
652         {
653             p_temp_item = p_temp_item->p_parent;
654             if( p_temp_item == p_node )
655             {
656                  vlc_mutex_unlock( &p_playlist->object_lock );
657                  vlc_object_release( p_playlist );
658                  return YES;
659             }
660         }
661         vlc_mutex_unlock( &p_playlist->object_lock );
662     }
663
664     vlc_object_release( p_playlist );
665     return NO;
666 }
667
668 /* This method is usefull for instance to remove the selected children of an
669    already selected node */
670 - (void)removeItemsFrom:(id)o_items ifChildrenOf:(id)o_nodes
671 {
672     unsigned int i, j;
673     for( i = 0 ; i < [o_items count] ; i++ )
674     {
675         for ( j = 0 ; j < [o_nodes count] ; j++ )
676         {
677             if( o_items == o_nodes)
678             {
679                 if( j == i ) continue;
680             }
681             if( [self isItem: [[o_items objectAtIndex:i] pointerValue]
682                     inNode: [[o_nodes objectAtIndex:j] pointerValue]
683                     checkItemExistence: NO] )
684             {
685                 [o_items removeObjectAtIndex:i];
686                 /* We need to execute the next iteration with the same index
687                    since the current item has been deleted */
688                 i--;
689                 break;
690             }
691         }
692     }
693
694 }
695
696 - (IBAction)savePlaylist:(id)sender
697 {
698     intf_thread_t * p_intf = VLCIntf;
699     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
700                                                        FIND_ANYWHERE );
701
702     NSSavePanel *o_save_panel = [NSSavePanel savePanel];
703     NSString * o_name = [NSString stringWithFormat: @"%@", _NS("Untitled")];
704
705     //[o_save_panel setAllowedFileTypes: [NSArray arrayWithObjects: @"m3u", @"xpf", nil] ];
706     [o_save_panel setTitle: _NS("Save Playlist")];
707     [o_save_panel setPrompt: _NS("Save")];
708     [o_save_panel setAccessoryView: o_save_accessory_view];
709
710     if( [o_save_panel runModalForDirectory: nil
711             file: o_name] == NSOKButton )
712     {
713         NSString *o_filename = [o_save_panel filename];
714
715         if( [o_save_accessory_popup indexOfSelectedItem] == 1 )
716         {
717             NSString * o_real_filename;
718             NSRange range;
719             range.location = [o_filename length] - [@".xspf" length];
720             range.length = [@".xspf" length];
721
722             if( [o_filename compare:@".xspf" options: NSCaseInsensitiveSearch
723                                              range: range] != NSOrderedSame )
724             {
725                 o_real_filename = [NSString stringWithFormat: @"%@.xspf", o_filename];
726             }
727             else
728             {
729                 o_real_filename = o_filename;
730             }
731             playlist_Export( p_playlist, 
732                 [o_real_filename fileSystemRepresentation], 
733                 p_playlist->p_local_category, "export-xspf" );
734         }
735         else
736         {
737             NSString * o_real_filename;
738             NSRange range;
739             range.location = [o_filename length] - [@".m3u" length];
740             range.length = [@".m3u" length];
741
742             if( [o_filename compare:@".m3u" options: NSCaseInsensitiveSearch
743                                              range: range] != NSOrderedSame )
744             {
745                 o_real_filename = [NSString stringWithFormat: @"%@.m3u", o_filename];
746             }
747             else
748             {
749                 o_real_filename = o_filename;
750             }
751             playlist_Export( p_playlist, 
752                 [o_real_filename fileSystemRepresentation],
753                 p_playlist->p_local_category, "export-m3u" );
754         }
755     }
756     vlc_object_release( p_playlist );
757 }
758
759 /* When called retrieves the selected outlineview row and plays that node or item */
760 - (IBAction)playItem:(id)sender
761 {
762     intf_thread_t * p_intf = VLCIntf;
763     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
764                                                        FIND_ANYWHERE );
765
766     if( p_playlist != NULL )
767     {
768         playlist_item_t *p_item;
769         playlist_item_t *p_node = NULL;
770
771         p_item = [[o_outline_view itemAtRow:[o_outline_view selectedRow]] pointerValue];
772
773         if( p_item )
774         {
775             if( p_item->i_children == -1 )
776             {
777                 p_node = p_item->p_parent;
778
779             }
780             else
781             {
782                 p_node = p_item;
783                 if( p_node->i_children > 0 && p_node->pp_children[0]->i_children == -1 )
784                 {
785                     p_item = p_node->pp_children[0];
786                 }
787                 else
788                 {
789                     p_item = NULL;
790                 }
791             }
792             playlist_Control( p_playlist, PLAYLIST_VIEWPLAY, 0, p_node, p_item );
793         }
794         vlc_object_release( p_playlist );
795     }
796 }
797
798 /* When called retrieves the selected outlineview row and plays that node or item */
799 - (IBAction)preparseItem:(id)sender
800 {
801     int i_count;
802     NSMutableArray *o_to_preparse;
803     intf_thread_t * p_intf = VLCIntf;
804     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
805                                                        FIND_ANYWHERE );
806                                                        
807     o_to_preparse = [NSMutableArray arrayWithArray:[[o_outline_view selectedRowEnumerator] allObjects]];
808     i_count = [o_to_preparse count];
809
810     if( p_playlist != NULL )
811     {
812         int i, i_row;
813         NSNumber *o_number;
814         playlist_item_t *p_item = NULL;
815
816         for( i = 0; i < i_count; i++ )
817         {
818             o_number = [o_to_preparse lastObject];
819             i_row = [o_number intValue];
820             p_item = [[o_outline_view itemAtRow:i_row] pointerValue];
821             [o_to_preparse removeObject: o_number];
822             [o_outline_view deselectRow: i_row];
823
824             if( p_item )
825             {
826                 if( p_item->i_children == -1 )
827                 {
828                     playlist_PreparseEnqueue( p_playlist, p_item->p_input );
829                 }
830                 else
831                 {
832                     msg_Dbg( p_intf, "preparse of nodes not yet implemented" );
833                 }
834             }
835         }
836         vlc_object_release( p_playlist );
837     }
838     [self playlistUpdated];
839 }
840
841 - (IBAction)servicesChange:(id)sender
842 {
843     NSMenuItem *o_mi = (NSMenuItem *)sender;
844     NSString *o_string = [o_mi representedObject];
845     playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
846                                           FIND_ANYWHERE );
847     if( !playlist_IsServicesDiscoveryLoaded( p_playlist, [o_string cString] ) )
848         playlist_ServicesDiscoveryAdd( p_playlist, [o_string cString] );
849     else
850         playlist_ServicesDiscoveryRemove( p_playlist, [o_string cString] );
851
852     [o_mi setState: playlist_IsServicesDiscoveryLoaded( p_playlist,
853                                           [o_string cString] ) ? YES : NO];
854
855     vlc_object_release( p_playlist );
856     [self playlistUpdated];
857     return;
858 }
859
860 - (IBAction)selectAll:(id)sender
861 {
862     [o_outline_view selectAll: nil];
863 }
864
865 - (IBAction)deleteItem:(id)sender
866 {
867     int i, i_count, i_row;
868     NSMutableArray *o_to_delete;
869     NSNumber *o_number;
870
871     playlist_t * p_playlist;
872     intf_thread_t * p_intf = VLCIntf;
873
874     p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
875                                           FIND_ANYWHERE );
876
877     if ( p_playlist == NULL )
878     {
879         return;
880     }
881     o_to_delete = [NSMutableArray arrayWithArray:[[o_outline_view selectedRowEnumerator] allObjects]];
882     i_count = [o_to_delete count];
883
884     for( i = 0; i < i_count; i++ )
885     {
886         o_number = [o_to_delete lastObject];
887         i_row = [o_number intValue];
888         id o_item = [o_outline_view itemAtRow: i_row];
889         playlist_item_t *p_item = [o_item pointerValue];
890         [o_to_delete removeObject: o_number];
891         [o_outline_view deselectRow: i_row];
892
893         if( [[o_outline_view dataSource] outlineView:o_outline_view
894                                         numberOfChildrenOfItem: o_item]  > 0 )
895         //is a node and not an item
896         {
897             if( p_playlist->status.i_status != PLAYLIST_STOPPED &&
898                 [self isItem: p_playlist->status.p_item inNode:
899                         ((playlist_item_t *)[o_item pointerValue])
900                         checkItemExistence: NO] == YES )
901             {
902                 // if current item is in selected node and is playing then stop playlist
903                 playlist_Stop( p_playlist );
904             }
905             vlc_mutex_lock( &p_playlist->object_lock );
906             playlist_NodeDelete( p_playlist, p_item, VLC_TRUE, VLC_FALSE );
907             vlc_mutex_unlock( &p_playlist->object_lock );
908         }
909         else
910         {
911             playlist_LockDelete( p_playlist, p_item->i_id );
912         }
913     }
914     [self playlistUpdated];
915     vlc_object_release( p_playlist );
916 }
917
918 - (IBAction)sortNodeByName:(id)sender
919 {
920     [self sortNode: SORT_TITLE];
921 }
922
923 - (IBAction)sortNodeByAuthor:(id)sender
924 {
925     [self sortNode: SORT_AUTHOR];
926 }
927
928 - (void)sortNode:(int)i_mode
929 {
930     playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
931                                           FIND_ANYWHERE );
932     playlist_item_t * p_item;
933
934     if (p_playlist == NULL)
935     {
936         return;
937     }
938
939     if( [o_outline_view selectedRow] > -1 )
940     {
941         p_item = [[o_outline_view itemAtRow: [o_outline_view selectedRow]]
942                                                                 pointerValue];
943     }
944     else
945     /*If no item is selected, sort the whole playlist*/
946     {
947         p_item = p_playlist->p_root_category;
948     }
949
950     if( p_item->i_children > -1 ) // the item is a node
951     {
952         vlc_mutex_lock( &p_playlist->object_lock );
953         playlist_RecursiveNodeSort( p_playlist, p_item, i_mode, ORDER_NORMAL );
954         vlc_mutex_unlock( &p_playlist->object_lock );
955     }
956     else
957     {
958         vlc_mutex_lock( &p_playlist->object_lock );
959         playlist_RecursiveNodeSort( p_playlist,
960                 p_item->p_parent, i_mode, ORDER_NORMAL );
961         vlc_mutex_unlock( &p_playlist->object_lock );
962     }
963     vlc_object_release( p_playlist );
964     [self playlistUpdated];
965 }
966
967 - (input_item_t *)createItem:(NSDictionary *)o_one_item
968 {
969     intf_thread_t * p_intf = VLCIntf;
970     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
971                                                        FIND_ANYWHERE );
972
973     if( p_playlist == NULL )
974     {
975         return NULL;
976     }
977     input_item_t *p_input;
978     int i;
979     BOOL b_rem = FALSE, b_dir = FALSE;
980     NSString *o_uri, *o_name;
981     NSArray *o_options;
982     NSURL *o_true_file;
983
984     /* Get the item */
985     o_uri = (NSString *)[o_one_item objectForKey: @"ITEM_URL"];
986     o_name = (NSString *)[o_one_item objectForKey: @"ITEM_NAME"];
987     o_options = (NSArray *)[o_one_item objectForKey: @"ITEM_OPTIONS"];
988
989     /* Find the name for a disc entry ( i know, can you believe the trouble?) */
990     if( ( !o_name || [o_name isEqualToString:@""] ) && [o_uri rangeOfString: @"/dev/"].location != NSNotFound )
991     {
992         int i_count, i_index;
993         struct statfs *mounts = NULL;
994
995         i_count = getmntinfo (&mounts, MNT_NOWAIT);
996         /* getmntinfo returns a pointer to static data. Do not free. */
997         for( i_index = 0 ; i_index < i_count; i_index++ )
998         {
999             NSMutableString *o_temp, *o_temp2;
1000             o_temp = [NSMutableString stringWithString: o_uri];
1001             o_temp2 = [NSMutableString stringWithCString: mounts[i_index].f_mntfromname];
1002             [o_temp replaceOccurrencesOfString: @"/dev/rdisk" withString: @"/dev/disk" options:nil range:NSMakeRange(0, [o_temp length]) ];
1003             [o_temp2 replaceOccurrencesOfString: @"s0" withString: @"" options:nil range:NSMakeRange(0, [o_temp2 length]) ];
1004             [o_temp2 replaceOccurrencesOfString: @"s1" withString: @"" options:nil range:NSMakeRange(0, [o_temp2 length]) ];
1005
1006             if( strstr( [o_temp fileSystemRepresentation], [o_temp2 fileSystemRepresentation] ) != NULL )
1007             {
1008                 o_name = [[NSFileManager defaultManager] displayNameAtPath: [NSString stringWithCString:mounts[i_index].f_mntonname]];
1009             }
1010         }
1011     }
1012     /* If no name, then make a guess */
1013     if( !o_name) o_name = [[NSFileManager defaultManager] displayNameAtPath: o_uri];
1014
1015     if( [[NSFileManager defaultManager] fileExistsAtPath:o_uri isDirectory:&b_dir] && b_dir &&
1016         [[NSWorkspace sharedWorkspace] getFileSystemInfoForPath: o_uri isRemovable: &b_rem
1017                 isWritable:NULL isUnmountable:NULL description:NULL type:NULL] && b_rem   )
1018     {
1019         /* All of this is to make sure CD's play when you D&D them on VLC */
1020         /* Converts mountpoint to a /dev file */
1021         struct statfs *buf;
1022         char *psz_dev;
1023         NSMutableString *o_temp;
1024
1025         buf = (struct statfs *) malloc (sizeof(struct statfs));
1026         statfs( [o_uri fileSystemRepresentation], buf );
1027         psz_dev = strdup(buf->f_mntfromname);
1028         o_temp = [NSMutableString stringWithCString: psz_dev ];
1029         [o_temp replaceOccurrencesOfString: @"/dev/disk" withString: @"/dev/rdisk" options:nil range:NSMakeRange(0, [o_temp length]) ];
1030         [o_temp replaceOccurrencesOfString: @"s0" withString: @"" options:nil range:NSMakeRange(0, [o_temp length]) ];
1031         [o_temp replaceOccurrencesOfString: @"s1" withString: @"" options:nil range:NSMakeRange(0, [o_temp length]) ];
1032         o_uri = o_temp;
1033     }
1034
1035     p_input = input_ItemNew( p_playlist, [o_uri fileSystemRepresentation], [o_name UTF8String] );
1036     if( !p_input )
1037        return NULL;
1038
1039     if( o_options )
1040     {
1041         for( i = 0; i < (int)[o_options count]; i++ )
1042         {
1043             vlc_input_item_AddOption( p_input, strdup( [[o_options objectAtIndex:i] UTF8String] ) );
1044         }
1045     }
1046
1047     /* Recent documents menu */
1048     o_true_file = [NSURL fileURLWithPath: o_uri];
1049     if( o_true_file != nil )
1050     {
1051         [[NSDocumentController sharedDocumentController]
1052             noteNewRecentDocumentURL: o_true_file];
1053     }
1054
1055     vlc_object_release( p_playlist );
1056     return p_input;
1057 }
1058
1059 - (void)appendArray:(NSArray*)o_array atPos:(int)i_position enqueue:(BOOL)b_enqueue
1060 {
1061     int i_item;
1062     playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
1063                                             FIND_ANYWHERE );
1064     if( p_playlist == NULL )
1065     {
1066         return;
1067     }
1068
1069     for( i_item = 0; i_item < (int)[o_array count]; i_item++ )
1070     {
1071         input_item_t *p_input;
1072         NSDictionary *o_one_item;
1073
1074         /* Get the item */
1075         o_one_item = [o_array objectAtIndex: i_item];
1076         p_input = [self createItem: o_one_item];
1077         if( !p_input )
1078         {
1079             continue;
1080         }
1081
1082         /* Add the item */
1083         playlist_PlaylistAddInput( p_playlist, p_input, PLAYLIST_INSERT,
1084                         i_position == -1 ? PLAYLIST_END : i_position + i_item );
1085
1086         if( i_item == 0 && !b_enqueue )
1087         {
1088             playlist_item_t *p_item;
1089             p_item = playlist_ItemGetByInput( p_playlist, p_input );
1090             playlist_Control( p_playlist, PLAYLIST_ITEMPLAY, p_item );
1091         }
1092         else
1093         {
1094             playlist_item_t *p_item;
1095             p_item = playlist_ItemGetByInput( p_playlist, p_input );
1096             playlist_Control( p_playlist, PLAYLIST_PREPARSE, p_item );
1097         }
1098     }
1099     [self playlistUpdated];
1100     vlc_object_release( p_playlist );
1101 }
1102
1103 - (void)appendNodeArray:(NSArray*)o_array inNode:(playlist_item_t *)p_node atPos:(int)i_position enqueue:(BOOL)b_enqueue
1104 {
1105     int i_item;
1106     playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
1107                                             FIND_ANYWHERE );
1108     if( p_playlist == NULL )
1109     {
1110         return;
1111     }
1112
1113     for( i_item = 0; i_item < (int)[o_array count]; i_item++ )
1114     {
1115         input_item_t *p_input;
1116         NSDictionary *o_one_item;
1117
1118         /* Get the item */
1119         o_one_item = [o_array objectAtIndex: i_item];
1120         p_input = [self createItem: o_one_item];
1121         if( !p_input )
1122         {
1123             continue;
1124         }
1125
1126         /* Add the item */
1127        playlist_NodeAddInput( p_playlist, p_input, p_node,
1128                                       PLAYLIST_INSERT,
1129                                       i_position == -1 ?
1130                                       PLAYLIST_END : i_position + i_item );
1131
1132
1133         if( i_item == 0 && !b_enqueue )
1134         {
1135             playlist_item_t *p_item;
1136             p_item = playlist_ItemGetByInput( p_playlist, p_input );
1137             playlist_Control( p_playlist, PLAYLIST_ITEMPLAY, p_item );
1138         }
1139         else
1140         {
1141             playlist_item_t *p_item;
1142             p_item = playlist_ItemGetByInput( p_playlist, p_input );
1143             playlist_Control( p_playlist, PLAYLIST_PREPARSE, p_item );
1144         }
1145     }
1146     [self playlistUpdated];
1147     vlc_object_release( p_playlist );
1148 }
1149
1150 - (IBAction)handlePopUp:(id)sender
1151
1152 {
1153     intf_thread_t * p_intf = VLCIntf;
1154     vlc_value_t val1,val2;
1155     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
1156                                             FIND_ANYWHERE );
1157     if( p_playlist == NULL )
1158     {
1159         return;
1160     }
1161
1162     switch( [o_loop_popup indexOfSelectedItem] )
1163     {
1164         case 1:
1165
1166              val1.b_bool = 0;
1167              var_Set( p_playlist, "loop", val1 );
1168              val1.b_bool = 1;
1169              var_Set( p_playlist, "repeat", val1 );
1170              vout_OSDMessage( p_intf, DEFAULT_CHAN, _( "Repeat One" ) );
1171         break;
1172
1173         case 2:
1174              val1.b_bool = 0;
1175              var_Set( p_playlist, "repeat", val1 );
1176              val1.b_bool = 1;
1177              var_Set( p_playlist, "loop", val1 );
1178              vout_OSDMessage( p_intf, DEFAULT_CHAN, _( "Repeat All" ) );
1179         break;
1180
1181         default:
1182              var_Get( p_playlist, "repeat", &val1 );
1183              var_Get( p_playlist, "loop", &val2 );
1184              if( val1.b_bool || val2.b_bool )
1185              {
1186                   val1.b_bool = 0;
1187                   var_Set( p_playlist, "repeat", val1 );
1188                   var_Set( p_playlist, "loop", val1 );
1189                   vout_OSDMessage( p_intf, DEFAULT_CHAN, _( "Repeat Off" ) );
1190              }
1191          break;
1192      }
1193      vlc_object_release( p_playlist );
1194      [self playlistUpdated];
1195 }
1196
1197 - (NSMutableArray *)subSearchItem:(playlist_item_t *)p_item
1198 {
1199     playlist_t *p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
1200                                                        FIND_ANYWHERE );
1201     playlist_item_t *p_selected_item;
1202     int i_current, i_selected_row;
1203
1204     if( !p_playlist )
1205         return NULL;
1206
1207     i_selected_row = [o_outline_view selectedRow];
1208     if (i_selected_row < 0)
1209         i_selected_row = 0;
1210
1211     p_selected_item = (playlist_item_t *)[[o_outline_view itemAtRow:
1212                                             i_selected_row] pointerValue];
1213
1214     for( i_current = 0; i_current < p_item->i_children ; i_current++ )
1215     {
1216         char *psz_temp;
1217         NSString *o_current_name, *o_current_author;
1218
1219         vlc_mutex_lock( &p_playlist->object_lock );
1220         o_current_name = [NSString stringWithUTF8String:
1221             p_item->pp_children[i_current]->p_input->psz_name];
1222         psz_temp = vlc_input_item_GetInfo( p_item->p_input ,
1223                    _("Meta-information"),_("Artist") );
1224         o_current_author = [NSString stringWithUTF8String: psz_temp];
1225         free( psz_temp);
1226         vlc_mutex_unlock( &p_playlist->object_lock );
1227
1228         if( p_selected_item == p_item->pp_children[i_current] &&
1229                     b_selected_item_met == NO )
1230         {
1231             b_selected_item_met = YES;
1232         }
1233         else if( p_selected_item == p_item->pp_children[i_current] &&
1234                     b_selected_item_met == YES )
1235         {
1236             vlc_object_release( p_playlist );
1237             return NULL;
1238         }
1239         else if( b_selected_item_met == YES &&
1240                     ( [o_current_name rangeOfString:[o_search_field
1241                         stringValue] options:NSCaseInsensitiveSearch ].length ||
1242                       [o_current_author rangeOfString:[o_search_field
1243                         stringValue] options:NSCaseInsensitiveSearch ].length ) )
1244         {
1245             vlc_object_release( p_playlist );
1246             /*Adds the parent items in the result array as well, so that we can
1247             expand the tree*/
1248             return [NSMutableArray arrayWithObject: [NSValue
1249                             valueWithPointer: p_item->pp_children[i_current]]];
1250         }
1251         if( p_item->pp_children[i_current]->i_children > 0 )
1252         {
1253             id o_result = [self subSearchItem:
1254                                             p_item->pp_children[i_current]];
1255             if( o_result != NULL )
1256             {
1257                 vlc_object_release( p_playlist );
1258                 [o_result insertObject: [NSValue valueWithPointer:
1259                                 p_item->pp_children[i_current]] atIndex:0];
1260                 return o_result;
1261             }
1262         }
1263     }
1264     vlc_object_release( p_playlist );
1265     return NULL;
1266 }
1267
1268 - (IBAction)searchItem:(id)sender
1269 {
1270     playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
1271                                                        FIND_ANYWHERE );
1272     id o_result;
1273
1274     unsigned int i;
1275     int i_row = -1;
1276
1277     b_selected_item_met = NO;
1278
1279     if( p_playlist == NULL )
1280         return;
1281
1282         /*First, only search after the selected item:*
1283          *(b_selected_item_met = NO)                 */
1284     o_result = [self subSearchItem:p_playlist->p_root_category];
1285     if( o_result == NULL )
1286     {
1287         /* If the first search failed, search again from the beginning */
1288         o_result = [self subSearchItem:p_playlist->p_root_category];
1289     }
1290     if( o_result != NULL )
1291     {
1292         int i_start;
1293         if( [[o_result objectAtIndex: 0] pointerValue] ==
1294                                                     p_playlist->p_local_category )
1295         i_start = 1;
1296         else
1297         i_start = 0;
1298
1299         for( i = i_start ; i < [o_result count] - 1 ; i++ )
1300         {
1301             [o_outline_view expandItem: [o_outline_dict objectForKey:
1302                         [NSString stringWithFormat: @"%p",
1303                         [[o_result objectAtIndex: i] pointerValue]]]];
1304         }
1305         i_row = [o_outline_view rowForItem: [o_outline_dict objectForKey:
1306                         [NSString stringWithFormat: @"%p",
1307                         [[o_result objectAtIndex: [o_result count] - 1 ]
1308                         pointerValue]]]];
1309     }
1310     if( i_row > -1 )
1311     {
1312         [o_outline_view selectRow:i_row byExtendingSelection: NO];
1313         [o_outline_view scrollRowToVisible: i_row];
1314     }
1315     vlc_object_release( p_playlist );
1316 }
1317
1318 - (IBAction)recursiveExpandNode:(id)sender
1319 {
1320     id o_item = [o_outline_view itemAtRow: [o_outline_view selectedRow]];
1321     playlist_item_t *p_item = (playlist_item_t *)[o_item pointerValue];
1322
1323     if( ![[o_outline_view dataSource] outlineView: o_outline_view
1324                                                     isItemExpandable: o_item] )
1325     {
1326         o_item = [o_outline_dict objectForKey: [NSString
1327                    stringWithFormat: @"%p", p_item->p_parent]];
1328     }
1329
1330     /* We need to collapse the node first, since OSX refuses to recursively
1331        expand an already expanded node, even if children nodes are collapsed. */
1332     [o_outline_view collapseItem: o_item collapseChildren: YES];
1333     [o_outline_view expandItem: o_item expandChildren: YES];
1334 }
1335
1336 - (NSMenu *)menuForEvent:(NSEvent *)o_event
1337 {
1338     NSPoint pt;
1339     vlc_bool_t b_rows;
1340     vlc_bool_t b_item_sel;
1341
1342     pt = [o_outline_view convertPoint: [o_event locationInWindow]
1343                                                  fromView: nil];
1344     b_item_sel = ( [o_outline_view rowAtPoint: pt] != -1 &&
1345                    [o_outline_view selectedRow] != -1 );
1346     b_rows = [o_outline_view numberOfRows] != 0;
1347
1348     [o_mi_play setEnabled: b_item_sel];
1349     [o_mi_delete setEnabled: b_item_sel];
1350     [o_mi_selectall setEnabled: b_rows];
1351     [o_mi_info setEnabled: b_item_sel];
1352     [o_mi_preparse setEnabled: b_item_sel];
1353     [o_mi_recursive_expand setEnabled: b_item_sel];
1354     [o_mi_sort_name setEnabled: b_item_sel];
1355     [o_mi_sort_author setEnabled: b_item_sel];
1356
1357     return( o_ctx_menu );
1358 }
1359
1360 - (void)outlineView: (NSTableView*)o_tv
1361                   didClickTableColumn:(NSTableColumn *)o_tc
1362 {
1363     int i_mode = 0, i_type;
1364     intf_thread_t *p_intf = VLCIntf;
1365
1366     playlist_t *p_playlist = (playlist_t *)vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
1367                                        FIND_ANYWHERE );
1368     if( p_playlist == NULL )
1369     {
1370         return;
1371     }
1372
1373     /* Check whether the selected table column header corresponds to a
1374        sortable table column*/
1375     if( !( o_tc == o_tc_name || o_tc == o_tc_author ) )
1376     {
1377         vlc_object_release( p_playlist );
1378         return;
1379     }
1380
1381     if( o_tc_sortColumn == o_tc )
1382     {
1383         b_isSortDescending = !b_isSortDescending;
1384     }
1385     else
1386     {
1387         b_isSortDescending = VLC_FALSE;
1388     }
1389
1390     if( o_tc == o_tc_name )
1391     {
1392         i_mode = SORT_TITLE;
1393     }
1394     else if( o_tc == o_tc_author )
1395     {
1396         i_mode = SORT_AUTHOR;
1397     }
1398
1399     if( b_isSortDescending )
1400     {
1401         i_type = ORDER_REVERSE;
1402     }
1403     else
1404     {
1405         i_type = ORDER_NORMAL;
1406     }
1407
1408     vlc_mutex_lock( &p_playlist->object_lock );
1409     playlist_RecursiveNodeSort( p_playlist, p_playlist->p_root_category, i_mode, i_type );
1410     vlc_mutex_unlock( &p_playlist->object_lock );
1411
1412     vlc_object_release( p_playlist );
1413     [self playlistUpdated];
1414
1415     o_tc_sortColumn = o_tc;
1416     [o_outline_view setHighlightedTableColumn:o_tc];
1417
1418     if( b_isSortDescending )
1419     {
1420         [o_outline_view setIndicatorImage:o_descendingSortingImage
1421                                                         inTableColumn:o_tc];
1422     }
1423     else
1424     {
1425         [o_outline_view setIndicatorImage:o_ascendingSortingImage
1426                                                         inTableColumn:o_tc];
1427     }
1428 }
1429
1430
1431 - (void)outlineView:(NSOutlineView *)outlineView
1432                                 willDisplayCell:(id)cell
1433                                 forTableColumn:(NSTableColumn *)tableColumn
1434                                 item:(id)item
1435 {
1436     playlist_t *p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
1437                                           FIND_ANYWHERE );
1438
1439     id o_playing_item;
1440
1441     if( !p_playlist ) return;
1442
1443     o_playing_item = [o_outline_dict objectForKey:
1444                 [NSString stringWithFormat:@"%p",  p_playlist->status.p_item]];
1445
1446     if( [self isItem: [o_playing_item pointerValue] inNode:
1447                         [item pointerValue] checkItemExistence: YES]
1448                         || [o_playing_item isEqual: item] )
1449     {
1450         [cell setFont: [NSFont boldSystemFontOfSize: 0]];
1451     }
1452     else
1453     {
1454         [cell setFont: [NSFont systemFontOfSize: 0]];
1455     }
1456     vlc_object_release( p_playlist );
1457 }
1458
1459 - (IBAction)addNode:(id)sender
1460 {
1461     /* we have to create a new thread here because otherwise we would block the
1462      * interface since the interaction-stuff and this code would run in the same
1463      * thread */
1464     [NSThread detachNewThreadSelector: @selector(addNodeThreadedly) 
1465         toTarget: self withObject:nil];
1466     [self playlistUpdated];
1467 }
1468
1469 - (void)addNodeThreadedly
1470 {
1471     NSAutoreleasePool * ourPool = [[NSAutoreleasePool alloc] init];
1472
1473     /* simply adds a new node to the end of the playlist */
1474     playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
1475                                           FIND_ANYWHERE );
1476     vlc_thread_set_priority( p_playlist, VLC_THREAD_PRIORITY_LOW );
1477
1478     if( !p_playlist )
1479     {
1480         return;
1481     }
1482
1483     int ret_v;
1484     char *psz_name = NULL;
1485     playlist_item_t * p_item;
1486     ret_v = intf_UserStringInput( p_playlist, _("New Node"), 
1487         _("Please enter a name for the new node."), &psz_name );
1488     if( psz_name != NULL && psz_name != "" )
1489         p_item = playlist_NodeCreate( p_playlist, psz_name, 
1490                                             p_playlist->p_local_category );
1491     else
1492         p_item = playlist_NodeCreate( p_playlist, _("Empty Folder"), 
1493                                             p_playlist->p_local_category );
1494
1495     if(! p_item )
1496         msg_Warn( VLCIntf, "node creation failed" );
1497
1498     vlc_object_release( p_playlist );
1499     [ourPool release];
1500 }
1501
1502 @end
1503
1504 @implementation VLCPlaylist (NSOutlineViewDataSource)
1505
1506 - (id)outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item
1507 {
1508     id o_value = [super outlineView: outlineView child: index ofItem: item];
1509     playlist_t *p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
1510                                                FIND_ANYWHERE );
1511
1512     if( !p_playlist ) return nil;
1513
1514     if( p_playlist->i_size >= 2 )
1515     {
1516         [o_status_field setStringValue: [NSString stringWithFormat:
1517                     _NS("%i items in the playlist"), p_playlist->i_size]];
1518     }
1519     else
1520     {
1521         if( p_playlist->i_size == 0 )
1522         {
1523             [o_status_field setStringValue: _NS("No items in the playlist")];
1524         }
1525         else
1526         {
1527             [o_status_field setStringValue: _NS("1 item in the playlist")];
1528         }
1529     }
1530     vlc_object_release( p_playlist );
1531
1532     [o_outline_dict setObject:o_value forKey:[NSString stringWithFormat:@"%p",
1533                                                     [o_value pointerValue]]];
1534 NSLog( @"add item %p", [o_value pointerValue] );
1535     return o_value;
1536
1537 }
1538
1539 /* Required for drag & drop and reordering */
1540 - (BOOL)outlineView:(NSOutlineView *)outlineView writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pboard
1541 {
1542     unsigned int i;
1543     playlist_t *p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
1544                                                FIND_ANYWHERE );
1545
1546     /* First remove the items that were moved during the last drag & drop
1547        operation */
1548     [o_items_array removeAllObjects];
1549     [o_nodes_array removeAllObjects];
1550
1551     if( !p_playlist ) return NO;
1552
1553     for( i = 0 ; i < [items count] ; i++ )
1554     {
1555         id o_item = [items objectAtIndex: i];
1556
1557         /* Refuse to move items that are not in the General Node
1558            (Service Discovery) */
1559         if( ![self isItem: [o_item pointerValue] inNode:
1560                         p_playlist->p_local_category checkItemExistence: NO])
1561         {
1562             vlc_object_release(p_playlist);
1563             return NO;
1564         }
1565         /* Fill the items and nodes to move in 2 different arrays */
1566         if( ((playlist_item_t *)[o_item pointerValue])->i_children > 0 )
1567             [o_nodes_array addObject: o_item];
1568         else
1569             [o_items_array addObject: o_item];
1570     }
1571
1572     /* Now we need to check if there are selected items that are in already
1573        selected nodes. In that case, we only want to move the nodes */
1574     [self removeItemsFrom: o_nodes_array ifChildrenOf: o_nodes_array];
1575     [self removeItemsFrom: o_items_array ifChildrenOf: o_nodes_array];
1576
1577     /* We add the "VLCPlaylistItemPboardType" type to be able to recognize
1578        a Drop operation coming from the playlist. */
1579
1580     [pboard declareTypes: [NSArray arrayWithObjects:
1581         @"VLCPlaylistItemPboardType", nil] owner: self];
1582     [pboard setData:[NSData data] forType:@"VLCPlaylistItemPboardType"];
1583
1584     vlc_object_release(p_playlist);
1585     return YES;
1586 }
1587
1588 - (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id <NSDraggingInfo>)info proposedItem:(id)item proposedChildIndex:(int)index
1589 {
1590     playlist_t *p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
1591                                                FIND_ANYWHERE );
1592     NSPasteboard *o_pasteboard = [info draggingPasteboard];
1593
1594     if( !p_playlist ) return NSDragOperationNone;
1595
1596     /* Dropping ON items is not allowed if item is not a node */
1597     if( item )
1598     {
1599         if( index == NSOutlineViewDropOnItemIndex &&
1600                 ((playlist_item_t *)[item pointerValue])->i_children == -1 )
1601         {
1602             vlc_object_release( p_playlist );
1603             return NSDragOperationNone;
1604         }
1605     }
1606
1607     /* We refuse to drop an item in anything else than a child of the General
1608        Node. We still accept items that would be root nodes of the outlineview
1609        however, to allow drop in an empty playlist. */
1610     if( !([self isItem: [item pointerValue] inNode: p_playlist->p_local_category
1611                                     checkItemExistence: NO] || item == nil) )
1612     {
1613         vlc_object_release( p_playlist );
1614         return NSDragOperationNone;
1615     }
1616
1617     /* Drop from the Playlist */
1618     if( [[o_pasteboard types] containsObject: @"VLCPlaylistItemPboardType"] )
1619     {
1620         unsigned int i;
1621         for( i = 0 ; i < [o_nodes_array count] ; i++ )
1622         {
1623             /* We refuse to Drop in a child of an item we are moving */
1624             if( [self isItem: [item pointerValue] inNode:
1625                     [[o_nodes_array objectAtIndex: i] pointerValue]
1626                     checkItemExistence: NO] )
1627             {
1628                 vlc_object_release( p_playlist );
1629                 return NSDragOperationNone;
1630             }
1631         }
1632         vlc_object_release(p_playlist);
1633         return NSDragOperationMove;
1634     }
1635
1636     /* Drop from the Finder */
1637     else if( [[o_pasteboard types] containsObject: NSFilenamesPboardType] )
1638     {
1639         vlc_object_release(p_playlist);
1640         return NSDragOperationGeneric;
1641     }
1642     vlc_object_release(p_playlist);
1643     return NSDragOperationNone;
1644 }
1645
1646 - (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id <NSDraggingInfo>)info item:(id)item childIndex:(int)index
1647 {
1648     playlist_t * p_playlist =  vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
1649                                                        FIND_ANYWHERE );
1650     NSPasteboard *o_pasteboard = [info draggingPasteboard];
1651
1652     if( !p_playlist ) return NO;
1653
1654     /* Drag & Drop inside the playlist */
1655     if( [[o_pasteboard types] containsObject: @"VLCPlaylistItemPboardType"] )
1656     {
1657         int i_row, i_removed_from_node = 0;
1658         unsigned int i;
1659         playlist_item_t *p_new_parent, *p_item = NULL;
1660         NSArray *o_all_items = [o_nodes_array arrayByAddingObjectsFromArray:
1661                                                                 o_items_array];
1662         /* If the item is to be dropped as root item of the outline, make it a
1663            child of the General node.
1664            Else, choose the proposed parent as parent. */
1665         if( item == nil ) p_new_parent = p_playlist->p_local_category;
1666         else p_new_parent = [item pointerValue];
1667
1668         /* Make sure the proposed parent is a node.
1669            (This should never be true) */
1670         if( p_new_parent->i_children < 0 )
1671         {
1672             vlc_object_release( p_playlist );
1673             return NO;
1674         }
1675
1676         for( i = 0; i < [o_all_items count]; i++ )
1677         {
1678             playlist_item_t *p_old_parent = NULL;
1679             int i_old_index = 0;
1680
1681             p_item = [[o_all_items objectAtIndex:i] pointerValue];
1682             p_old_parent = p_item->p_parent;
1683             if( !p_old_parent )
1684             continue;
1685             /* We may need the old index later */
1686             if( p_new_parent == p_old_parent )
1687             {
1688                 int j;
1689                 for( j = 0; j < p_old_parent->i_children; j++ )
1690                 {
1691                     if( p_old_parent->pp_children[j] == p_item )
1692                     {
1693                         i_old_index = j;
1694                         break;
1695                     }
1696                 }
1697             }
1698
1699             vlc_mutex_lock( &p_playlist->object_lock );
1700             // Acually detach the item from the old position
1701             if( playlist_NodeRemoveItem( p_playlist, p_item, p_old_parent ) ==
1702                 VLC_SUCCESS )
1703             {
1704                 int i_new_index;
1705                 /* Calculate the new index */
1706                 if( index == -1 )
1707                 i_new_index = -1;
1708                 /* If we move the item in the same node, we need to take into
1709                    account that one item will be deleted */
1710                 else
1711                 {
1712                     if ((p_new_parent == p_old_parent &&
1713                                    i_old_index < index + (int)i) )
1714                     {
1715                         i_removed_from_node++;
1716                     }
1717                     i_new_index = index + i - i_removed_from_node;
1718                 }
1719                 // Reattach the item to the new position
1720                 playlist_NodeInsert( p_playlist, p_item, p_new_parent, i_new_index );
1721             }
1722             vlc_mutex_unlock( &p_playlist->object_lock );
1723         }
1724         [self playlistUpdated];
1725         i_row = [o_outline_view rowForItem:[o_outline_dict
1726             objectForKey:[NSString stringWithFormat: @"%p",
1727             [[o_all_items objectAtIndex: 0] pointerValue]]]];
1728
1729         if( i_row == -1 )
1730         {
1731             i_row = [o_outline_view rowForItem:[o_outline_dict
1732             objectForKey:[NSString stringWithFormat: @"%p", p_new_parent]]];
1733         }
1734
1735         [o_outline_view deselectAll: self];
1736         [o_outline_view selectRow: i_row byExtendingSelection: NO];
1737         [o_outline_view scrollRowToVisible: i_row];
1738
1739         vlc_object_release(p_playlist);
1740         return YES;
1741     }
1742
1743     else if( [[o_pasteboard types] containsObject: NSFilenamesPboardType] )
1744     {
1745         int i;
1746         playlist_item_t *p_node = [item pointerValue];
1747
1748         NSArray *o_array = [NSArray array];
1749         NSArray *o_values = [[o_pasteboard propertyListForType:
1750                                         NSFilenamesPboardType]
1751                                 sortedArrayUsingSelector:
1752                                         @selector(caseInsensitiveCompare:)];
1753
1754         for( i = 0; i < (int)[o_values count]; i++)
1755         {
1756             NSDictionary *o_dic;
1757             o_dic = [NSDictionary dictionaryWithObject:[o_values
1758                         objectAtIndex:i] forKey:@"ITEM_URL"];
1759             o_array = [o_array arrayByAddingObject: o_dic];
1760         }
1761
1762         if ( item == nil )
1763         {
1764             [self appendArray: o_array atPos: index enqueue: YES];
1765         }
1766         /* This should never occur */
1767         else if( p_node->i_children == -1 )
1768         {
1769             vlc_object_release( p_playlist );
1770             return NO;
1771         }
1772         else
1773         {
1774             [self appendNodeArray: o_array inNode: p_node
1775                 atPos: index enqueue: YES];
1776         }
1777         vlc_object_release( p_playlist );
1778         return YES;
1779     }
1780     vlc_object_release( p_playlist );
1781     return NO;
1782 }
1783
1784 /* Delegate method of NSWindow */
1785 /*- (void)windowWillClose:(NSNotification *)aNotification
1786 {
1787     [o_btn_playlist setState: NSOffState];
1788 }
1789 */
1790 @end
1791
1792