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