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