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