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