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