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