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