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