]> git.sesse.net Git - vlc/blob - modules/gui/macosx/playlist.m
Merge back branch 0.8.6-playlist-vlm to trunk.
[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, [o_real_filename fileSystemRepresentation], "export-xspf" );
745         }
746         else
747         {
748             NSString * o_real_filename;
749             NSRange range;
750             range.location = [o_filename length] - [@".m3u" length];
751             range.length = [@".m3u" length];
752
753             if( [o_filename compare:@".m3u" options: NSCaseInsensitiveSearch
754                                              range: range] != NSOrderedSame )
755             {
756                 o_real_filename = [NSString stringWithFormat: @"%@.m3u", o_filename];
757             }
758             else
759             {
760                 o_real_filename = o_filename;
761             }
762             playlist_Export( p_playlist, [o_real_filename fileSystemRepresentation], p_playlist->p_local_category, "export-m3u" );
763         }
764     }
765     vlc_object_release( p_playlist );
766 }
767
768 /* When called retrieves the selected outlineview row and plays that node or item */
769 - (IBAction)playItem:(id)sender
770 {
771     intf_thread_t * p_intf = VLCIntf;
772     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
773                                                        FIND_ANYWHERE );
774
775     if( p_playlist != NULL )
776     {
777         playlist_item_t *p_item;
778         playlist_item_t *p_node = NULL;
779
780         p_item = [[o_outline_view itemAtRow:[o_outline_view selectedRow]] pointerValue];
781
782         if( p_item )
783         {
784             if( p_item->i_children == -1 )
785             {
786                 p_node = p_item->p_parent;
787
788             }
789             else
790             {
791                 p_node = p_item;
792                 if( p_node->i_children > 0 && p_node->pp_children[0]->i_children == -1 )
793                 {
794                     p_item = p_node->pp_children[0];
795                 }
796                 else
797                 {
798                     p_item = NULL;
799                 }
800             }
801             playlist_Control( p_playlist, PLAYLIST_VIEWPLAY, 0, p_node, p_item );
802         }
803         vlc_object_release( p_playlist );
804     }
805 }
806
807 /* When called retrieves the selected outlineview row and plays that node or item */
808 - (IBAction)preparseItem:(id)sender
809 {
810     int i_count;
811     NSMutableArray *o_to_preparse;
812     intf_thread_t * p_intf = VLCIntf;
813     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
814                                                        FIND_ANYWHERE );
815                                                        
816     o_to_preparse = [NSMutableArray arrayWithArray:[[o_outline_view selectedRowEnumerator] allObjects]];
817     i_count = [o_to_preparse count];
818
819     if( p_playlist != NULL )
820     {
821         int i, i_row;
822         NSNumber *o_number;
823         playlist_item_t *p_item = NULL;
824
825         for( i = 0; i < i_count; i++ )
826         {
827             o_number = [o_to_preparse lastObject];
828             i_row = [o_number intValue];
829             p_item = [[o_outline_view itemAtRow:i_row] pointerValue];
830             [o_to_preparse removeObject: o_number];
831             [o_outline_view deselectRow: i_row];
832
833             if( p_item )
834             {
835                 if( p_item->i_children == -1 )
836                 {
837                     playlist_PreparseEnqueue( p_playlist, p_item->p_input );
838                 }
839                 else
840                 {
841                     msg_Dbg( p_intf, "preparse of nodes not yet implemented" );
842                 }
843             }
844         }
845         vlc_object_release( p_playlist );
846     }
847     [self playlistUpdated];
848 }
849
850 - (IBAction)servicesChange:(id)sender
851 {
852     NSMenuItem *o_mi = (NSMenuItem *)sender;
853     NSString *o_string = [o_mi representedObject];
854     playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
855                                           FIND_ANYWHERE );
856     if( !playlist_IsServicesDiscoveryLoaded( p_playlist, [o_string cString] ) )
857         playlist_ServicesDiscoveryAdd( p_playlist, [o_string cString] );
858     else
859         playlist_ServicesDiscoveryRemove( p_playlist, [o_string cString] );
860
861     [o_mi setState: playlist_IsServicesDiscoveryLoaded( p_playlist,
862                                           [o_string cString] ) ? YES : NO];
863
864     vlc_object_release( p_playlist );
865     [self playlistUpdated];
866     return;
867 }
868
869 - (IBAction)selectAll:(id)sender
870 {
871     [o_outline_view selectAll: nil];
872 }
873
874 - (IBAction)deleteItem:(id)sender
875 {
876     int i, i_count, i_row;
877     NSMutableArray *o_to_delete;
878     NSNumber *o_number;
879
880     playlist_t * p_playlist;
881     intf_thread_t * p_intf = VLCIntf;
882
883     p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
884                                           FIND_ANYWHERE );
885
886     if ( p_playlist == NULL )
887     {
888         return;
889     }
890     o_to_delete = [NSMutableArray arrayWithArray:[[o_outline_view selectedRowEnumerator] allObjects]];
891     i_count = [o_to_delete count];
892
893     for( i = 0; i < i_count; i++ )
894     {
895         o_number = [o_to_delete lastObject];
896         i_row = [o_number intValue];
897         id o_item = [o_outline_view itemAtRow: i_row];
898         playlist_item_t *p_item = [o_item pointerValue];
899         [o_to_delete removeObject: o_number];
900         [o_outline_view deselectRow: i_row];
901
902         if( [[o_outline_view dataSource] outlineView:o_outline_view
903                                         numberOfChildrenOfItem: o_item]  > 0 )
904         //is a node and not an item
905         {
906             if( p_playlist->status.i_status != PLAYLIST_STOPPED &&
907                 [self isItem: p_playlist->status.p_item inNode:
908                         ((playlist_item_t *)[o_item pointerValue])
909                         checkItemExistence: NO] == YES )
910             {
911                 // if current item is in selected node and is playing then stop playlist
912                 playlist_Stop( p_playlist );
913             }
914             vlc_mutex_lock( &p_playlist->object_lock );
915             playlist_NodeDelete( p_playlist, p_item, VLC_TRUE, VLC_FALSE );
916             vlc_mutex_unlock( &p_playlist->object_lock );
917         }
918         else
919         {
920             playlist_LockDelete( p_playlist, p_item->i_id );
921         }
922     }
923     [self playlistUpdated];
924     vlc_object_release( p_playlist );
925 }
926
927 - (IBAction)sortNodeByName:(id)sender
928 {
929     [self sortNode: SORT_TITLE];
930 }
931
932 - (IBAction)sortNodeByAuthor:(id)sender
933 {
934     [self sortNode: SORT_AUTHOR];
935 }
936
937 - (void)sortNode:(int)i_mode
938 {
939     playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
940                                           FIND_ANYWHERE );
941     playlist_item_t * p_item;
942
943     if (p_playlist == NULL)
944     {
945         return;
946     }
947
948     if( [o_outline_view selectedRow] > -1 )
949     {
950         p_item = [[o_outline_view itemAtRow: [o_outline_view selectedRow]]
951                                                                 pointerValue];
952     }
953     else
954     /*If no item is selected, sort the whole playlist*/
955     {
956         p_item = p_playlist->p_root_category;
957     }
958
959     if( p_item->i_children > -1 ) // the item is a node
960     {
961         vlc_mutex_lock( &p_playlist->object_lock );
962         playlist_RecursiveNodeSort( p_playlist, p_item, i_mode, ORDER_NORMAL );
963         vlc_mutex_unlock( &p_playlist->object_lock );
964     }
965     else
966     {
967         vlc_mutex_lock( &p_playlist->object_lock );
968         playlist_RecursiveNodeSort( p_playlist,
969                 p_item->p_parent, i_mode, ORDER_NORMAL );
970         vlc_mutex_unlock( &p_playlist->object_lock );
971     }
972     vlc_object_release( p_playlist );
973     [self playlistUpdated];
974 }
975
976 - (input_item_t *)createItem:(NSDictionary *)o_one_item
977 {
978     intf_thread_t * p_intf = VLCIntf;
979     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
980                                                        FIND_ANYWHERE );
981
982     if( p_playlist == NULL )
983     {
984         return NULL;
985     }
986     input_item_t *p_input;
987     int i;
988     BOOL b_rem = FALSE, b_dir = FALSE;
989     NSString *o_uri, *o_name;
990     NSArray *o_options;
991     NSURL *o_true_file;
992
993     /* Get the item */
994     o_uri = (NSString *)[o_one_item objectForKey: @"ITEM_URL"];
995     o_name = (NSString *)[o_one_item objectForKey: @"ITEM_NAME"];
996     o_options = (NSArray *)[o_one_item objectForKey: @"ITEM_OPTIONS"];
997
998     /* Find the name for a disc entry ( i know, can you believe the trouble?) */
999     if( ( !o_name || [o_name isEqualToString:@""] ) && [o_uri rangeOfString: @"/dev/"].location != NSNotFound )
1000     {
1001         int i_count, i_index;
1002         struct statfs *mounts = NULL;
1003
1004         i_count = getmntinfo (&mounts, MNT_NOWAIT);
1005         /* getmntinfo returns a pointer to static data. Do not free. */
1006         for( i_index = 0 ; i_index < i_count; i_index++ )
1007         {
1008             NSMutableString *o_temp, *o_temp2;
1009             o_temp = [NSMutableString stringWithString: o_uri];
1010             o_temp2 = [NSMutableString stringWithCString: mounts[i_index].f_mntfromname];
1011             [o_temp replaceOccurrencesOfString: @"/dev/rdisk" withString: @"/dev/disk" options:nil range:NSMakeRange(0, [o_temp length]) ];
1012             [o_temp2 replaceOccurrencesOfString: @"s0" withString: @"" options:nil range:NSMakeRange(0, [o_temp2 length]) ];
1013             [o_temp2 replaceOccurrencesOfString: @"s1" withString: @"" options:nil range:NSMakeRange(0, [o_temp2 length]) ];
1014
1015             if( strstr( [o_temp fileSystemRepresentation], [o_temp2 fileSystemRepresentation] ) != NULL )
1016             {
1017                 o_name = [[NSFileManager defaultManager] displayNameAtPath: [NSString stringWithCString:mounts[i_index].f_mntonname]];
1018             }
1019         }
1020     }
1021     /* If no name, then make a guess */
1022     if( !o_name) o_name = [[NSFileManager defaultManager] displayNameAtPath: o_uri];
1023
1024     if( [[NSFileManager defaultManager] fileExistsAtPath:o_uri isDirectory:&b_dir] && b_dir &&
1025         [[NSWorkspace sharedWorkspace] getFileSystemInfoForPath: o_uri isRemovable: &b_rem
1026                 isWritable:NULL isUnmountable:NULL description:NULL type:NULL] && b_rem   )
1027     {
1028         /* All of this is to make sure CD's play when you D&D them on VLC */
1029         /* Converts mountpoint to a /dev file */
1030         struct statfs *buf;
1031         char *psz_dev;
1032         NSMutableString *o_temp;
1033
1034         buf = (struct statfs *) malloc (sizeof(struct statfs));
1035         statfs( [o_uri fileSystemRepresentation], buf );
1036         psz_dev = strdup(buf->f_mntfromname);
1037         o_temp = [NSMutableString stringWithCString: psz_dev ];
1038         [o_temp replaceOccurrencesOfString: @"/dev/disk" withString: @"/dev/rdisk" options:nil range:NSMakeRange(0, [o_temp length]) ];
1039         [o_temp replaceOccurrencesOfString: @"s0" withString: @"" options:nil range:NSMakeRange(0, [o_temp length]) ];
1040         [o_temp replaceOccurrencesOfString: @"s1" withString: @"" options:nil range:NSMakeRange(0, [o_temp length]) ];
1041         o_uri = o_temp;
1042     }
1043
1044     p_input = input_ItemNew( p_playlist, [o_uri fileSystemRepresentation], [o_name UTF8String] );
1045     if( !p_input )
1046        return NULL;
1047
1048     if( o_options )
1049     {
1050         for( i = 0; i < (int)[o_options count]; i++ )
1051         {
1052             vlc_input_item_AddOption( p_input, strdup( [[o_options objectAtIndex:i] UTF8String] ) );
1053         }
1054     }
1055
1056     /* Recent documents menu */
1057     o_true_file = [NSURL fileURLWithPath: o_uri];
1058     if( o_true_file != nil )
1059     {
1060         [[NSDocumentController sharedDocumentController]
1061             noteNewRecentDocumentURL: o_true_file];
1062     }
1063
1064     vlc_object_release( p_playlist );
1065     return p_input;
1066 }
1067
1068 - (void)appendArray:(NSArray*)o_array atPos:(int)i_position enqueue:(BOOL)b_enqueue
1069 {
1070     int i_item;
1071     playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
1072                                             FIND_ANYWHERE );
1073     if( p_playlist == NULL )
1074     {
1075         return;
1076     }
1077
1078     for( i_item = 0; i_item < (int)[o_array count]; i_item++ )
1079     {
1080         input_item_t *p_input;
1081         NSDictionary *o_one_item;
1082
1083         /* Get the item */
1084         o_one_item = [o_array objectAtIndex: i_item];
1085         p_input = [self createItem: o_one_item];
1086         if( !p_input )
1087         {
1088             continue;
1089         }
1090
1091         /* Add the item */
1092         playlist_PlaylistAddInput( p_playlist, p_input, PLAYLIST_INSERT,
1093                         i_position == -1 ? PLAYLIST_END : i_position + i_item );
1094
1095         if( i_item == 0 && !b_enqueue )
1096         {
1097             playlist_item_t *p_item;
1098             p_item = playlist_ItemGetByInput( p_playlist, p_input );
1099             playlist_Control( p_playlist, PLAYLIST_ITEMPLAY, p_item );
1100         }
1101         else
1102         {
1103             playlist_item_t *p_item;
1104             p_item = playlist_ItemGetByInput( p_playlist, p_input );
1105             playlist_Control( p_playlist, PLAYLIST_PREPARSE, p_item );
1106         }
1107     }
1108     [self playlistUpdated];
1109     vlc_object_release( p_playlist );
1110 }
1111
1112 - (void)appendNodeArray:(NSArray*)o_array inNode:(playlist_item_t *)p_node atPos:(int)i_position enqueue:(BOOL)b_enqueue
1113 {
1114     int i_item;
1115     playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
1116                                             FIND_ANYWHERE );
1117     if( p_playlist == NULL )
1118     {
1119         return;
1120     }
1121
1122     for( i_item = 0; i_item < (int)[o_array count]; i_item++ )
1123     {
1124         input_item_t *p_input;
1125         NSDictionary *o_one_item;
1126
1127         /* Get the item */
1128         o_one_item = [o_array objectAtIndex: i_item];
1129         p_input = [self createItem: o_one_item];
1130         if( !p_input )
1131         {
1132             continue;
1133         }
1134
1135         /* Add the item */
1136        playlist_NodeAddInput( p_playlist, p_input, p_node,
1137                                       PLAYLIST_INSERT,
1138                                       i_position == -1 ?
1139                                       PLAYLIST_END : i_position + i_item );
1140
1141
1142         if( i_item == 0 && !b_enqueue )
1143         {
1144             playlist_item_t *p_item;
1145             p_item = playlist_ItemGetByInput( p_playlist, p_input );
1146             playlist_Control( p_playlist, PLAYLIST_ITEMPLAY, p_item );
1147         }
1148         else
1149         {
1150             playlist_item_t *p_item;
1151             p_item = playlist_ItemGetByInput( p_playlist, p_input );
1152             playlist_Control( p_playlist, PLAYLIST_PREPARSE, p_item );
1153         }
1154     }
1155     [self playlistUpdated];
1156     vlc_object_release( p_playlist );
1157 }
1158
1159 - (IBAction)handlePopUp:(id)sender
1160
1161 {
1162     intf_thread_t * p_intf = VLCIntf;
1163     vlc_value_t val1,val2;
1164     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
1165                                             FIND_ANYWHERE );
1166     if( p_playlist == NULL )
1167     {
1168         return;
1169     }
1170
1171     switch( [o_loop_popup indexOfSelectedItem] )
1172     {
1173         case 1:
1174
1175              val1.b_bool = 0;
1176              var_Set( p_playlist, "loop", val1 );
1177              val1.b_bool = 1;
1178              var_Set( p_playlist, "repeat", val1 );
1179              vout_OSDMessage( p_intf, DEFAULT_CHAN, _( "Repeat One" ) );
1180         break;
1181
1182         case 2:
1183              val1.b_bool = 0;
1184              var_Set( p_playlist, "repeat", val1 );
1185              val1.b_bool = 1;
1186              var_Set( p_playlist, "loop", val1 );
1187              vout_OSDMessage( p_intf, DEFAULT_CHAN, _( "Repeat All" ) );
1188         break;
1189
1190         default:
1191              var_Get( p_playlist, "repeat", &val1 );
1192              var_Get( p_playlist, "loop", &val2 );
1193              if( val1.b_bool || val2.b_bool )
1194              {
1195                   val1.b_bool = 0;
1196                   var_Set( p_playlist, "repeat", val1 );
1197                   var_Set( p_playlist, "loop", val1 );
1198                   vout_OSDMessage( p_intf, DEFAULT_CHAN, _( "Repeat Off" ) );
1199              }
1200          break;
1201      }
1202      vlc_object_release( p_playlist );
1203      [self playlistUpdated];
1204 }
1205
1206 - (NSMutableArray *)subSearchItem:(playlist_item_t *)p_item
1207 {
1208     playlist_t *p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
1209                                                        FIND_ANYWHERE );
1210     playlist_item_t *p_selected_item;
1211     int i_current, i_selected_row;
1212
1213     if( !p_playlist )
1214         return NULL;
1215
1216     i_selected_row = [o_outline_view selectedRow];
1217     if (i_selected_row < 0)
1218         i_selected_row = 0;
1219
1220     p_selected_item = (playlist_item_t *)[[o_outline_view itemAtRow:
1221                                             i_selected_row] pointerValue];
1222
1223     for( i_current = 0; i_current < p_item->i_children ; i_current++ )
1224     {
1225         char *psz_temp;
1226         NSString *o_current_name, *o_current_author;
1227
1228         vlc_mutex_lock( &p_playlist->object_lock );
1229         o_current_name = [NSString stringWithUTF8String:
1230             p_item->pp_children[i_current]->p_input->psz_name];
1231         psz_temp = vlc_input_item_GetInfo( p_item->p_input ,
1232                    _("Meta-information"),_("Artist") );
1233         o_current_author = [NSString stringWithUTF8String: psz_temp];
1234         free( psz_temp);
1235         vlc_mutex_unlock( &p_playlist->object_lock );
1236
1237         if( p_selected_item == p_item->pp_children[i_current] &&
1238                     b_selected_item_met == NO )
1239         {
1240             b_selected_item_met = YES;
1241         }
1242         else if( p_selected_item == p_item->pp_children[i_current] &&
1243                     b_selected_item_met == YES )
1244         {
1245             vlc_object_release( p_playlist );
1246             return NULL;
1247         }
1248         else if( b_selected_item_met == YES &&
1249                     ( [o_current_name rangeOfString:[o_search_field
1250                         stringValue] options:NSCaseInsensitiveSearch ].length ||
1251                       [o_current_author rangeOfString:[o_search_field
1252                         stringValue] options:NSCaseInsensitiveSearch ].length ) )
1253         {
1254             vlc_object_release( p_playlist );
1255             /*Adds the parent items in the result array as well, so that we can
1256             expand the tree*/
1257             return [NSMutableArray arrayWithObject: [NSValue
1258                             valueWithPointer: p_item->pp_children[i_current]]];
1259         }
1260         if( p_item->pp_children[i_current]->i_children > 0 )
1261         {
1262             id o_result = [self subSearchItem:
1263                                             p_item->pp_children[i_current]];
1264             if( o_result != NULL )
1265             {
1266                 vlc_object_release( p_playlist );
1267                 [o_result insertObject: [NSValue valueWithPointer:
1268                                 p_item->pp_children[i_current]] atIndex:0];
1269                 return o_result;
1270             }
1271         }
1272     }
1273     vlc_object_release( p_playlist );
1274     return NULL;
1275 }
1276
1277 - (IBAction)searchItem:(id)sender
1278 {
1279     playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
1280                                                        FIND_ANYWHERE );
1281     id o_result;
1282
1283     unsigned int i;
1284     int i_row = -1;
1285
1286     b_selected_item_met = NO;
1287
1288     if( p_playlist == NULL )
1289         return;
1290
1291         /*First, only search after the selected item:*
1292          *(b_selected_item_met = NO)                 */
1293     o_result = [self subSearchItem:p_playlist->p_root_category];
1294     if( o_result == NULL )
1295     {
1296         /* If the first search failed, search again from the beginning */
1297         o_result = [self subSearchItem:p_playlist->p_root_category];
1298     }
1299     if( o_result != NULL )
1300     {
1301         int i_start;
1302         if( [[o_result objectAtIndex: 0] pointerValue] ==
1303                                                     p_playlist->p_local_category )
1304         i_start = 1;
1305         else
1306         i_start = 0;
1307
1308         for( i = i_start ; i < [o_result count] - 1 ; i++ )
1309         {
1310             [o_outline_view expandItem: [o_outline_dict objectForKey:
1311                         [NSString stringWithFormat: @"%p",
1312                         [[o_result objectAtIndex: i] pointerValue]]]];
1313         }
1314         i_row = [o_outline_view rowForItem: [o_outline_dict objectForKey:
1315                         [NSString stringWithFormat: @"%p",
1316                         [[o_result objectAtIndex: [o_result count] - 1 ]
1317                         pointerValue]]]];
1318     }
1319     if( i_row > -1 )
1320     {
1321         [o_outline_view selectRow:i_row byExtendingSelection: NO];
1322         [o_outline_view scrollRowToVisible: i_row];
1323     }
1324     vlc_object_release( p_playlist );
1325 }
1326
1327 - (IBAction)recursiveExpandNode:(id)sender
1328 {
1329     id o_item = [o_outline_view itemAtRow: [o_outline_view selectedRow]];
1330     playlist_item_t *p_item = (playlist_item_t *)[o_item pointerValue];
1331
1332     if( ![[o_outline_view dataSource] outlineView: o_outline_view
1333                                                     isItemExpandable: o_item] )
1334     {
1335         o_item = [o_outline_dict objectForKey: [NSString
1336                    stringWithFormat: @"%p", p_item->p_parent]];
1337     }
1338
1339     /* We need to collapse the node first, since OSX refuses to recursively
1340        expand an already expanded node, even if children nodes are collapsed. */
1341     [o_outline_view collapseItem: o_item collapseChildren: YES];
1342     [o_outline_view expandItem: o_item expandChildren: YES];
1343 }
1344
1345 - (NSMenu *)menuForEvent:(NSEvent *)o_event
1346 {
1347     NSPoint pt;
1348     vlc_bool_t b_rows;
1349     vlc_bool_t b_item_sel;
1350
1351     pt = [o_outline_view convertPoint: [o_event locationInWindow]
1352                                                  fromView: nil];
1353     b_item_sel = ( [o_outline_view rowAtPoint: pt] != -1 &&
1354                    [o_outline_view selectedRow] != -1 );
1355     b_rows = [o_outline_view numberOfRows] != 0;
1356
1357     [o_mi_play setEnabled: b_item_sel];
1358     [o_mi_delete setEnabled: b_item_sel];
1359     [o_mi_selectall setEnabled: b_rows];
1360     [o_mi_info setEnabled: b_item_sel];
1361     [o_mi_preparse setEnabled: b_item_sel];
1362     [o_mi_recursive_expand setEnabled: b_item_sel];
1363     [o_mi_sort_name setEnabled: b_item_sel];
1364     [o_mi_sort_author setEnabled: b_item_sel];
1365
1366     return( o_ctx_menu );
1367 }
1368
1369 - (void)outlineView: (NSTableView*)o_tv
1370                   didClickTableColumn:(NSTableColumn *)o_tc
1371 {
1372     int i_mode = 0, i_type;
1373     intf_thread_t *p_intf = VLCIntf;
1374
1375     playlist_t *p_playlist = (playlist_t *)vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
1376                                        FIND_ANYWHERE );
1377     if( p_playlist == NULL )
1378     {
1379         return;
1380     }
1381
1382     /* Check whether the selected table column header corresponds to a
1383        sortable table column*/
1384     if( !( o_tc == o_tc_name || o_tc == o_tc_author ) )
1385     {
1386         vlc_object_release( p_playlist );
1387         return;
1388     }
1389
1390     if( o_tc_sortColumn == o_tc )
1391     {
1392         b_isSortDescending = !b_isSortDescending;
1393     }
1394     else
1395     {
1396         b_isSortDescending = VLC_FALSE;
1397     }
1398
1399     if( o_tc == o_tc_name )
1400     {
1401         i_mode = SORT_TITLE;
1402     }
1403     else if( o_tc == o_tc_author )
1404     {
1405         i_mode = SORT_AUTHOR;
1406     }
1407
1408     if( b_isSortDescending )
1409     {
1410         i_type = ORDER_REVERSE;
1411     }
1412     else
1413     {
1414         i_type = ORDER_NORMAL;
1415     }
1416
1417     vlc_mutex_lock( &p_playlist->object_lock );
1418     playlist_RecursiveNodeSort( p_playlist, p_playlist->p_root_category, i_mode, i_type );
1419     vlc_mutex_unlock( &p_playlist->object_lock );
1420
1421     vlc_object_release( p_playlist );
1422     [self playlistUpdated];
1423
1424     o_tc_sortColumn = o_tc;
1425     [o_outline_view setHighlightedTableColumn:o_tc];
1426
1427     if( b_isSortDescending )
1428     {
1429         [o_outline_view setIndicatorImage:o_descendingSortingImage
1430                                                         inTableColumn:o_tc];
1431     }
1432     else
1433     {
1434         [o_outline_view setIndicatorImage:o_ascendingSortingImage
1435                                                         inTableColumn:o_tc];
1436     }
1437 }
1438
1439
1440 - (void)outlineView:(NSOutlineView *)outlineView
1441                                 willDisplayCell:(id)cell
1442                                 forTableColumn:(NSTableColumn *)tableColumn
1443                                 item:(id)item
1444 {
1445     playlist_t *p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
1446                                           FIND_ANYWHERE );
1447
1448     id o_playing_item;
1449
1450     if( !p_playlist ) return;
1451
1452     o_playing_item = [o_outline_dict objectForKey:
1453                 [NSString stringWithFormat:@"%p",  p_playlist->status.p_item]];
1454
1455     if( [self isItem: [o_playing_item pointerValue] inNode:
1456                         [item pointerValue] checkItemExistence: YES]
1457                         || [o_playing_item isEqual: item] )
1458     {
1459         [cell setFont: [NSFont boldSystemFontOfSize: 0]];
1460     }
1461     else
1462     {
1463         [cell setFont: [NSFont systemFontOfSize: 0]];
1464     }
1465     vlc_object_release( p_playlist );
1466 }
1467
1468 - (IBAction)addNode:(id)sender
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     if( !p_playlist )
1474     {
1475         return;
1476     }
1477
1478     playlist_item_t * p_item = playlist_NodeCreate( p_playlist,
1479                                 _("Empty Folder"), p_playlist->p_local_category );
1480
1481     if(! p_item )
1482         msg_Warn( VLCIntf, "node creation failed" );
1483
1484     vlc_object_release( p_playlist );
1485     [self playlistUpdated];
1486 }
1487
1488 @end
1489
1490 @implementation VLCPlaylist (NSOutlineViewDataSource)
1491
1492 - (id)outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item
1493 {
1494     id o_value = [super outlineView: outlineView child: index ofItem: item];
1495     playlist_t *p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
1496                                                FIND_ANYWHERE );
1497
1498     if( !p_playlist ) return nil;
1499
1500     if( p_playlist->i_size >= 2 )
1501     {
1502         [o_status_field setStringValue: [NSString stringWithFormat:
1503                     _NS("%i items in the playlist"), p_playlist->i_size]];
1504     }
1505     else
1506     {
1507         if( p_playlist->i_size == 0 )
1508         {
1509             [o_status_field setStringValue: _NS("No items in the playlist")];
1510         }
1511         else
1512         {
1513             [o_status_field setStringValue: _NS("1 item in the playlist")];
1514         }
1515     }
1516     vlc_object_release( p_playlist );
1517
1518     [o_outline_dict setObject:o_value forKey:[NSString stringWithFormat:@"%p",
1519                                                     [o_value pointerValue]]];
1520
1521     return o_value;
1522
1523 }
1524
1525 /* Required for drag & drop and reordering */
1526 - (BOOL)outlineView:(NSOutlineView *)outlineView writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pboard
1527 {
1528     unsigned int i;
1529     playlist_t *p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
1530                                                FIND_ANYWHERE );
1531
1532     /* First remove the items that were moved during the last drag & drop
1533        operation */
1534     [o_items_array removeAllObjects];
1535     [o_nodes_array removeAllObjects];
1536
1537     if( !p_playlist ) return NO;
1538
1539     for( i = 0 ; i < [items count] ; i++ )
1540     {
1541         id o_item = [items objectAtIndex: i];
1542
1543         /* Refuse to move items that are not in the General Node
1544            (Service Discovery) */
1545         if( ![self isItem: [o_item pointerValue] inNode:
1546                         p_playlist->p_local_category checkItemExistence: NO])
1547         {
1548             vlc_object_release(p_playlist);
1549             return NO;
1550         }
1551         /* Fill the items and nodes to move in 2 different arrays */
1552         if( ((playlist_item_t *)[o_item pointerValue])->i_children > 0 )
1553             [o_nodes_array addObject: o_item];
1554         else
1555             [o_items_array addObject: o_item];
1556     }
1557
1558     /* Now we need to check if there are selected items that are in already
1559        selected nodes. In that case, we only want to move the nodes */
1560     [self removeItemsFrom: o_nodes_array ifChildrenOf: o_nodes_array];
1561     [self removeItemsFrom: o_items_array ifChildrenOf: o_nodes_array];
1562
1563     /* We add the "VLCPlaylistItemPboardType" type to be able to recognize
1564        a Drop operation coming from the playlist. */
1565
1566     [pboard declareTypes: [NSArray arrayWithObjects:
1567         @"VLCPlaylistItemPboardType", nil] owner: self];
1568     [pboard setData:[NSData data] forType:@"VLCPlaylistItemPboardType"];
1569
1570     vlc_object_release(p_playlist);
1571     return YES;
1572 }
1573
1574 - (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id <NSDraggingInfo>)info proposedItem:(id)item proposedChildIndex:(int)index
1575 {
1576     playlist_t *p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
1577                                                FIND_ANYWHERE );
1578     NSPasteboard *o_pasteboard = [info draggingPasteboard];
1579
1580     if( !p_playlist ) return NSDragOperationNone;
1581
1582     /* Dropping ON items is not allowed if item is not a node */
1583     if( item )
1584     {
1585         if( index == NSOutlineViewDropOnItemIndex &&
1586                 ((playlist_item_t *)[item pointerValue])->i_children == -1 )
1587         {
1588             vlc_object_release( p_playlist );
1589             return NSDragOperationNone;
1590         }
1591     }
1592
1593     /* We refuse to drop an item in anything else than a child of the General
1594        Node. We still accept items that would be root nodes of the outlineview
1595        however, to allow drop in an empty playlist. */
1596     if( !([self isItem: [item pointerValue] inNode: p_playlist->p_local_category
1597                                     checkItemExistence: NO] || item == nil) )
1598     {
1599         vlc_object_release( p_playlist );
1600         return NSDragOperationNone;
1601     }
1602
1603     /* Drop from the Playlist */
1604     if( [[o_pasteboard types] containsObject: @"VLCPlaylistItemPboardType"] )
1605     {
1606         unsigned int i;
1607         for( i = 0 ; i < [o_nodes_array count] ; i++ )
1608         {
1609             /* We refuse to Drop in a child of an item we are moving */
1610             if( [self isItem: [item pointerValue] inNode:
1611                     [[o_nodes_array objectAtIndex: i] pointerValue]
1612                     checkItemExistence: NO] )
1613             {
1614                 vlc_object_release( p_playlist );
1615                 return NSDragOperationNone;
1616             }
1617         }
1618         vlc_object_release(p_playlist);
1619         return NSDragOperationMove;
1620     }
1621
1622     /* Drop from the Finder */
1623     else if( [[o_pasteboard types] containsObject: NSFilenamesPboardType] )
1624     {
1625         vlc_object_release(p_playlist);
1626         return NSDragOperationGeneric;
1627     }
1628     vlc_object_release(p_playlist);
1629     return NSDragOperationNone;
1630 }
1631
1632 - (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id <NSDraggingInfo>)info item:(id)item childIndex:(int)index
1633 {
1634     playlist_t * p_playlist =  vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
1635                                                        FIND_ANYWHERE );
1636     NSPasteboard *o_pasteboard = [info draggingPasteboard];
1637
1638     if( !p_playlist ) return NO;
1639
1640     /* Drag & Drop inside the playlist */
1641     if( [[o_pasteboard types] containsObject: @"VLCPlaylistItemPboardType"] )
1642     {
1643         int i_row, i_removed_from_node = 0;
1644         unsigned int i;
1645         playlist_item_t *p_new_parent, *p_item = NULL;
1646         NSArray *o_all_items = [o_nodes_array arrayByAddingObjectsFromArray:
1647                                                                 o_items_array];
1648         /* If the item is to be dropped as root item of the outline, make it a
1649            child of the General node.
1650            Else, choose the proposed parent as parent. */
1651         if( item == nil ) p_new_parent = p_playlist->p_local_category;
1652         else p_new_parent = [item pointerValue];
1653
1654         /* Make sure the proposed parent is a node.
1655            (This should never be true) */
1656         if( p_new_parent->i_children < 0 )
1657         {
1658             vlc_object_release( p_playlist );
1659             return NO;
1660         }
1661
1662         for( i = 0; i < [o_all_items count]; i++ )
1663         {
1664             playlist_item_t *p_old_parent = NULL;
1665             int i_old_index = 0;
1666
1667             p_item = [[o_all_items objectAtIndex:i] pointerValue];
1668             p_old_parent = p_item->p_parent;
1669             if( !p_old_parent )
1670             continue;
1671             /* We may need the old index later */
1672             if( p_new_parent == p_old_parent )
1673             {
1674                 int j;
1675                 for( j = 0; j < p_old_parent->i_children; j++ )
1676                 {
1677                     if( p_old_parent->pp_children[j] == p_item )
1678                     {
1679                         i_old_index = j;
1680                         break;
1681                     }
1682                 }
1683             }
1684
1685             vlc_mutex_lock( &p_playlist->object_lock );
1686             // Acually detach the item from the old position
1687             if( playlist_NodeRemoveItem( p_playlist, p_item, p_old_parent ) ==
1688                 VLC_SUCCESS )
1689             {
1690                 int i_new_index;
1691                 /* Calculate the new index */
1692                 if( index == -1 )
1693                 i_new_index = -1;
1694                 /* If we move the item in the same node, we need to take into
1695                    account that one item will be deleted */
1696                 else
1697                 {
1698                     if ((p_new_parent == p_old_parent &&
1699                                    i_old_index < index + (int)i) )
1700                     {
1701                         i_removed_from_node++;
1702                     }
1703                     i_new_index = index + i - i_removed_from_node;
1704                 }
1705                 // Reattach the item to the new position
1706                 playlist_NodeInsert( p_playlist, p_item, p_new_parent, i_new_index );
1707             }
1708             vlc_mutex_unlock( &p_playlist->object_lock );
1709         }
1710         [self playlistUpdated];
1711         i_row = [o_outline_view rowForItem:[o_outline_dict
1712             objectForKey:[NSString stringWithFormat: @"%p",
1713             [[o_all_items objectAtIndex: 0] pointerValue]]]];
1714
1715         if( i_row == -1 )
1716         {
1717             i_row = [o_outline_view rowForItem:[o_outline_dict
1718             objectForKey:[NSString stringWithFormat: @"%p", p_new_parent]]];
1719         }
1720
1721         [o_outline_view deselectAll: self];
1722         [o_outline_view selectRow: i_row byExtendingSelection: NO];
1723         [o_outline_view scrollRowToVisible: i_row];
1724
1725         vlc_object_release(p_playlist);
1726         return YES;
1727     }
1728
1729     else if( [[o_pasteboard types] containsObject: NSFilenamesPboardType] )
1730     {
1731         int i;
1732         playlist_item_t *p_node = [item pointerValue];
1733
1734         NSArray *o_array = [NSArray array];
1735         NSArray *o_values = [[o_pasteboard propertyListForType:
1736                                         NSFilenamesPboardType]
1737                                 sortedArrayUsingSelector:
1738                                         @selector(caseInsensitiveCompare:)];
1739
1740         for( i = 0; i < (int)[o_values count]; i++)
1741         {
1742             NSDictionary *o_dic;
1743             o_dic = [NSDictionary dictionaryWithObject:[o_values
1744                         objectAtIndex:i] forKey:@"ITEM_URL"];
1745             o_array = [o_array arrayByAddingObject: o_dic];
1746         }
1747
1748         if ( item == nil )
1749         {
1750             [self appendArray: o_array atPos: index enqueue: YES];
1751         }
1752         /* This should never occur */
1753         else if( p_node->i_children == -1 )
1754         {
1755             vlc_object_release( p_playlist );
1756             return NO;
1757         }
1758         else
1759         {
1760             [self appendNodeArray: o_array inNode: p_node
1761                 atPos: index enqueue: YES];
1762         }
1763         vlc_object_release( p_playlist );
1764         return YES;
1765     }
1766     vlc_object_release( p_playlist );
1767     return NO;
1768 }
1769
1770 /* Delegate method of NSWindow */
1771 /*- (void)windowWillClose:(NSNotification *)aNotification
1772 {
1773     [o_btn_playlist setState: NSOffState];
1774 }
1775 */
1776 @end
1777
1778