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