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