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