]> git.sesse.net Git - vlc/blob - modules/gui/macosx/playlist.m
* added a button to create an empty node at the top level of the playlist. Thanks...
[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     /* Change the simple textfield into a searchField if we can... */
467 #if 0
468     if( MACOS_VERSION >= 10.3 )
469     {
470         NSView *o_parentview = [o_status_field superview];
471         NSSearchField *o_better_search_field = [[NSSearchField alloc]initWithFrame:[o_search_field frame]];
472         [o_better_search_field setRecentsAutosaveName:@"VLC media player search"];
473         [o_better_search_field setDelegate:self];
474         [[NSNotificationCenter defaultCenter] addObserver: self
475             selector: @selector(searchfieldChanged:)
476             name: NSControlTextDidChangeNotification
477             object: o_better_search_field];
478
479         [o_better_search_field setTarget:self];
480         [o_better_search_field setAction:@selector(searchItem:)];
481
482         [o_better_search_field setAutoresizingMask:NSViewMinXMargin];
483         [o_parentview addSubview:o_better_search_field];
484         [o_search_field setHidden:YES];
485     }
486 #endif
487     //[self playlistUpdated];
488 }
489
490 - (void)searchfieldChanged:(NSNotification *)o_notification
491 {
492     [o_search_field setStringValue:[[o_notification object] stringValue]];
493 }
494
495 - (void)initStrings
496 {
497     [super initStrings];
498
499     [o_mi_save_playlist setTitle: _NS("Save Playlist...")];
500     [o_mi_play setTitle: _NS("Play")];
501     [o_mi_delete setTitle: _NS("Delete")];
502     [o_mi_recursive_expand setTitle: _NS("Expand Node")];
503     [o_mi_selectall setTitle: _NS("Select All")];
504     [o_mi_info setTitle: _NS("Properties")];
505     [o_mi_preparse setTitle: _NS("Preparse")];
506     [o_mi_sort_name setTitle: _NS("Sort Node by Name")];
507     [o_mi_sort_author setTitle: _NS("Sort Node by Author")];
508     [o_mi_services setTitle: _NS("Services discovery")];
509     [o_status_field setStringValue: [NSString stringWithFormat:
510                         _NS("No items in the playlist")]];
511
512     [o_random_ckb setTitle: _NS("Random")];
513 #if 0
514     [o_search_button setTitle: _NS("Search")];
515 #endif
516     [o_search_field setToolTip: _NS("Search in Playlist")];
517     [[o_loop_popup itemAtIndex:0] setTitle: _NS("Standard Play")];
518     [[o_loop_popup itemAtIndex:1] setTitle: _NS("Repeat One")];
519     [[o_loop_popup itemAtIndex:2] setTitle: _NS("Repeat All")];
520     [o_mi_addNode setTitle: _NS("Add Folder to Playlist")];
521 }
522
523 - (void)playlistUpdated
524 {
525     unsigned int i;
526
527     /* Clear indications of any existing column sorting*/
528     for( i = 0 ; i < [[o_outline_view tableColumns] count] ; i++ )
529     {
530         [o_outline_view setIndicatorImage:nil inTableColumn:
531                             [[o_outline_view tableColumns] objectAtIndex:i]];
532     }
533
534     [o_outline_view setHighlightedTableColumn:nil];
535     o_tc_sortColumn = nil;
536     // TODO Find a way to keep the dict size to a minimum
537     //[o_outline_dict removeAllObjects];
538     [o_outline_view reloadData];
539     [[[[VLCMain sharedInstance] getWizard] getPlaylistWizard] reloadOutlineView];
540     [[[[VLCMain sharedInstance] getBookmarks] getDataTable] reloadData];
541 }
542
543 - (void)playModeUpdated
544 {
545     playlist_t *p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
546                                           FIND_ANYWHERE );
547     vlc_value_t val, val2;
548
549     if( p_playlist == NULL )
550     {
551         return;
552     }
553
554     var_Get( p_playlist, "loop", &val2 );
555     var_Get( p_playlist, "repeat", &val );
556     if( val.b_bool == VLC_TRUE )
557     {
558         [o_loop_popup selectItemAtIndex: 1];
559    }
560     else if( val2.b_bool == VLC_TRUE )
561     {
562         [o_loop_popup selectItemAtIndex: 2];
563     }
564     else
565     {
566         [o_loop_popup selectItemAtIndex: 0];
567     }
568
569     var_Get( p_playlist, "random", &val );
570     [o_random_ckb setState: val.b_bool];
571
572     vlc_object_release( p_playlist );
573 }
574
575 - (playlist_item_t *)parentOfItem:(playlist_item_t *)p_item
576 {
577     int i;
578     for( i = 0 ; i < p_item->i_parents; i++ )
579     {
580         if( p_item->pp_parents[i]->i_view == i_current_view )
581         {
582             return p_item->pp_parents[i]->p_parent;
583         }
584     }
585     return NULL;
586 }
587
588 - (void)updateRowSelection
589 {
590     int i_row;
591     unsigned int j;
592
593     playlist_t *p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
594                                           FIND_ANYWHERE );
595     playlist_item_t *p_item, *p_temp_item;
596     NSMutableArray *o_array = [NSMutableArray array];
597
598     if( p_playlist == NULL )
599         return;
600
601     p_item = p_playlist->status.p_item;
602     if( p_item == NULL )
603     {
604         vlc_object_release(p_playlist);
605         return;
606     }
607
608     p_temp_item = p_item;
609     while( p_temp_item->i_parents > 0 )
610     {
611         [o_array insertObject: [NSValue valueWithPointer: p_temp_item] atIndex: 0];
612
613         p_temp_item = [self parentOfItem: p_temp_item];
614         /*for (i = 0 ; i < p_temp_item->i_parents ; i++)
615         {
616             if( p_temp_item->pp_parents[i]->i_view == i_current_view )
617             {
618                 p_temp_item = p_temp_item->pp_parents[i]->p_parent;
619                 break;
620             }
621         }*/
622     }
623
624     for (j = 0 ; j < [o_array count] - 1 ; j++)
625     {
626         id o_item;
627         if( ( o_item = [o_outline_dict objectForKey:
628                             [NSString stringWithFormat: @"%p",
629                             [[o_array objectAtIndex:j] pointerValue]]] ) != nil )
630             [o_outline_view expandItem: o_item];
631
632     }
633
634     i_row = [o_outline_view rowForItem:[o_outline_dict
635             objectForKey:[NSString stringWithFormat: @"%p", p_item]]];
636
637     [o_outline_view selectRow: i_row byExtendingSelection: NO];
638     [o_outline_view scrollRowToVisible: i_row];
639
640     vlc_object_release(p_playlist);
641 }
642
643 /* Check if p_item is a child of p_node recursively. We need to check the item
644    existence first since OSX sometimes tries to redraw items that have been
645    deleted. We don't do it when not required  since this verification takes
646    quite a long time on big playlists (yes, pretty hacky). */
647 - (BOOL)isItem: (playlist_item_t *)p_item
648                     inNode: (playlist_item_t *)p_node
649                     checkItemExistence:(BOOL)b_check
650
651 {
652     playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
653                                           FIND_ANYWHERE );
654     playlist_item_t *p_temp_item = p_item;
655
656     if( p_playlist == NULL )
657     {
658         return NO;
659     }
660
661     if( p_node == p_item )
662     {
663         vlc_object_release(p_playlist);
664         return YES;
665     }
666
667     if( p_node->i_children < 1)
668     {
669         vlc_object_release(p_playlist);
670         return NO;
671     }
672
673     if ( p_temp_item )
674     {
675         int i;
676         vlc_mutex_lock( &p_playlist->object_lock );
677
678         if( b_check )
679         {
680         /* Since outlineView: willDisplayCell:... may call this function with
681            p_items that don't exist anymore, first check if the item is still
682            in the playlist. Any cleaner solution welcomed. */
683             for( i = 0; i < p_playlist->i_all_size; i++ )
684             {
685                 if( p_playlist->pp_all_items[i] == p_item ) break;
686                 else if ( i == p_playlist->i_all_size - 1 )
687                 {
688                     vlc_object_release( p_playlist );
689                     vlc_mutex_unlock( &p_playlist->object_lock );
690                     return NO;
691                 }
692             }
693         }
694
695         while( p_temp_item->i_parents > 0 )
696         {
697             p_temp_item = [self parentOfItem: p_temp_item];
698             if( p_temp_item == p_node )
699             {
700                  vlc_mutex_unlock( &p_playlist->object_lock );
701                  vlc_object_release( p_playlist );
702                  return YES;
703             }
704
705 /*            for( i = 0; i < p_temp_item->i_parents ; i++ )
706             {
707                 if( p_temp_item->pp_parents[i]->i_view == i_current_view )
708                 {
709                     if( p_temp_item->pp_parents[i]->p_parent == p_node )
710                     {
711                         vlc_mutex_unlock( &p_playlist->object_lock );
712                         vlc_object_release( p_playlist );
713                         return YES;
714                     }
715                     else
716                     {
717                         p_temp_item = p_temp_item->pp_parents[i]->p_parent;
718                         break;
719                     }
720                 }
721             }*/
722         }
723         vlc_mutex_unlock( &p_playlist->object_lock );
724     }
725
726     vlc_object_release( p_playlist );
727     return NO;
728 }
729
730 /* This method is usefull for instance to remove the selected children of an
731    already selected node */
732 - (void)removeItemsFrom:(id)o_items ifChildrenOf:(id)o_nodes
733 {
734     unsigned int i, j;
735     for( i = 0 ; i < [o_items count] ; i++ )
736     {
737         for ( j = 0 ; j < [o_nodes count] ; j++ )
738         {
739             if( o_items == o_nodes)
740             {
741                 if( j == i ) continue;
742             }
743             if( [self isItem: [[o_items objectAtIndex:i] pointerValue]
744                     inNode: [[o_nodes objectAtIndex:j] pointerValue]
745                     checkItemExistence: NO] )
746             {
747                 [o_items removeObjectAtIndex:i];
748                 /* We need to execute the next iteration with the same index
749                    since the current item has been deleted */
750                 i--;
751                 break;
752             }
753         }
754     }
755
756 }
757
758 - (IBAction)savePlaylist:(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     NSSavePanel *o_save_panel = [NSSavePanel savePanel];
765     NSString * o_name = [NSString stringWithFormat: @"%@.m3u", _NS("Untitled")];
766     [o_save_panel setTitle: _NS("Save Playlist")];
767     [o_save_panel setPrompt: _NS("Save")];
768
769     if( [o_save_panel runModalForDirectory: nil
770             file: o_name] == NSOKButton )
771     {
772         playlist_Export( p_playlist, [[o_save_panel filename] fileSystemRepresentation], "export-m3u" );
773     }
774     vlc_object_release( p_playlist );
775 }
776
777
778 /* When called retrieves the selected outlineview row and plays that node or item */
779 - (IBAction)playItem:(id)sender
780 {
781     intf_thread_t * p_intf = VLCIntf;
782     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
783                                                        FIND_ANYWHERE );
784
785     if( p_playlist != NULL )
786     {
787         playlist_item_t *p_item;
788         playlist_item_t *p_node = NULL;
789
790         p_item = [[o_outline_view itemAtRow:[o_outline_view selectedRow]] pointerValue];
791
792         if( p_item )
793         {
794             if( p_item->i_children == -1 )
795             {
796                 p_node = [self parentOfItem: p_item];
797
798 /*                for( i = 0 ; i < p_item->i_parents ; i++ )
799                 {
800                     if( p_item->pp_parents[i]->i_view == i_current_view )
801                     {
802                         p_node = p_item->pp_parents[i]->p_parent;
803                     }
804                 }*/
805             }
806             else
807             {
808                 p_node = p_item;
809                 if( p_node->i_children > 0 && p_node->pp_children[0]->i_children == -1 )
810                 {
811                     p_item = p_node->pp_children[0];
812                 }
813                 else
814                 {
815                     p_item = NULL;
816                 }
817             }
818             playlist_Control( p_playlist, PLAYLIST_VIEWPLAY, i_current_view, p_node, p_item );
819         }
820         vlc_object_release( p_playlist );
821     }
822 }
823
824 /* When called retrieves the selected outlineview row and plays that node or item */
825 - (IBAction)preparseItem:(id)sender
826 {
827     int i_count;
828     NSMutableArray *o_to_preparse;
829     intf_thread_t * p_intf = VLCIntf;
830     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
831                                                        FIND_ANYWHERE );
832                                                        
833     o_to_preparse = [NSMutableArray arrayWithArray:[[o_outline_view selectedRowEnumerator] allObjects]];
834     i_count = [o_to_preparse count];
835
836     if( p_playlist != NULL )
837     {
838         int i, i_row;
839         NSNumber *o_number;
840         playlist_item_t *p_item = NULL;
841
842         for( i = 0; i < i_count; i++ )
843         {
844             o_number = [o_to_preparse lastObject];
845             i_row = [o_number intValue];
846             p_item = [[o_outline_view itemAtRow:i_row] pointerValue];
847             [o_to_preparse removeObject: o_number];
848             [o_outline_view deselectRow: i_row];
849
850             if( p_item )
851             {
852                 if( p_item->i_children == -1 )
853                 {
854                     playlist_PreparseEnqueue( p_playlist, &p_item->input );
855                 }
856                 else
857                 {
858                     msg_Dbg( p_intf, "preparse of nodes not yet implemented" );
859                 }
860             }
861         }
862         vlc_object_release( p_playlist );
863     }
864     [self playlistUpdated];
865 }
866
867 - (IBAction)servicesChange:(id)sender
868 {
869     NSMenuItem *o_mi = (NSMenuItem *)sender;
870     NSString *o_string = [o_mi representedObject];
871     playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
872                                           FIND_ANYWHERE );
873     if( !playlist_IsServicesDiscoveryLoaded( p_playlist, [o_string cString] ) )
874         playlist_ServicesDiscoveryAdd( p_playlist, [o_string cString] );
875     else
876         playlist_ServicesDiscoveryRemove( p_playlist, [o_string cString] );
877
878     [o_mi setState: playlist_IsServicesDiscoveryLoaded( p_playlist,
879                                           [o_string cString] ) ? YES : NO];
880
881     i_current_view = VIEW_CATEGORY;
882     playlist_ViewUpdate( p_playlist, i_current_view );
883     vlc_object_release( p_playlist );
884     [self playlistUpdated];
885     return;
886 }
887
888 - (IBAction)selectAll:(id)sender
889 {
890     [o_outline_view selectAll: nil];
891 }
892
893 - (IBAction)deleteItem:(id)sender
894 {
895     int i, i_count, i_row;
896     NSMutableArray *o_to_delete;
897     NSNumber *o_number;
898
899     playlist_t * p_playlist;
900     intf_thread_t * p_intf = VLCIntf;
901
902     p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
903                                           FIND_ANYWHERE );
904
905     if ( p_playlist == NULL )
906     {
907         return;
908     }
909     o_to_delete = [NSMutableArray arrayWithArray:[[o_outline_view selectedRowEnumerator] allObjects]];
910     i_count = [o_to_delete count];
911
912     for( i = 0; i < i_count; i++ )
913     {
914         o_number = [o_to_delete lastObject];
915         i_row = [o_number intValue];
916         id o_item = [o_outline_view itemAtRow: i_row];
917         playlist_item_t *p_item = [o_item pointerValue];
918         [o_to_delete removeObject: o_number];
919         [o_outline_view deselectRow: i_row];
920
921         if( [[o_outline_view dataSource] outlineView:o_outline_view
922                                         numberOfChildrenOfItem: o_item]  > 0 )
923         //is a node and not an item
924         {
925             if( p_playlist->status.i_status != PLAYLIST_STOPPED &&
926                 [self isItem: p_playlist->status.p_item inNode:
927                         ((playlist_item_t *)[o_item pointerValue])
928                         checkItemExistence: NO] == YES )
929             {
930                 // if current item is in selected node and is playing then stop playlist
931                 playlist_Stop( p_playlist );
932             }
933             vlc_mutex_lock( &p_playlist->object_lock );
934             playlist_NodeDelete( p_playlist, p_item, VLC_TRUE, VLC_FALSE );
935             vlc_mutex_unlock( &p_playlist->object_lock );
936         }
937         else
938         {
939             if( p_playlist->status.i_status != PLAYLIST_STOPPED &&
940                 p_playlist->status.p_item == [[o_outline_view itemAtRow: i_row] pointerValue] )
941             {
942                 playlist_Stop( p_playlist );
943             }
944             vlc_mutex_lock( &p_playlist->object_lock );
945             playlist_Delete( p_playlist, p_item->input.i_id );
946             vlc_mutex_unlock( &p_playlist->object_lock );
947         }
948     }
949     [self playlistUpdated];
950     vlc_object_release( p_playlist );
951 }
952
953 - (IBAction)sortNodeByName:(id)sender
954 {
955     [self sortNode: SORT_TITLE];
956 }
957
958 - (IBAction)sortNodeByAuthor:(id)sender
959 {
960     [self sortNode: SORT_AUTHOR];
961 }
962
963 - (void)sortNode:(int)i_mode
964 {
965     playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
966                                           FIND_ANYWHERE );
967     playlist_item_t * p_item;
968
969     if (p_playlist == NULL)
970     {
971         return;
972     }
973
974     if( [o_outline_view selectedRow] > -1 )
975     {
976         p_item = [[o_outline_view itemAtRow: [o_outline_view selectedRow]]
977                                                                 pointerValue];
978     }
979     else
980     /*If no item is selected, sort the whole playlist*/
981     {
982         playlist_view_t * p_view = playlist_ViewFind( p_playlist, i_current_view );
983         p_item = p_view->p_root;
984     }
985
986     if( p_item->i_children > -1 ) // the item is a node
987     {
988         vlc_mutex_lock( &p_playlist->object_lock );
989         playlist_RecursiveNodeSort( p_playlist, p_item, i_mode, ORDER_NORMAL );
990         vlc_mutex_unlock( &p_playlist->object_lock );
991     }
992     else
993     {
994         int i;
995
996         for( i = 0 ; i < p_item->i_parents ; i++ )
997         {
998             if( p_item->pp_parents[i]->i_view == i_current_view )
999             {
1000                 vlc_mutex_lock( &p_playlist->object_lock );
1001                 playlist_RecursiveNodeSort( p_playlist,
1002                         p_item->pp_parents[i]->p_parent, i_mode, ORDER_NORMAL );
1003                 vlc_mutex_unlock( &p_playlist->object_lock );
1004                 break;
1005             }
1006         }
1007     }
1008     vlc_object_release( p_playlist );
1009     [self playlistUpdated];
1010 }
1011
1012 - (playlist_item_t *)createItem:(NSDictionary *)o_one_item
1013 {
1014     intf_thread_t * p_intf = VLCIntf;
1015     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
1016                                                        FIND_ANYWHERE );
1017
1018     if( p_playlist == NULL )
1019     {
1020         return NULL;
1021     }
1022     playlist_item_t *p_item;
1023     int i;
1024     BOOL b_rem = FALSE, b_dir = FALSE;
1025     NSString *o_uri, *o_name;
1026     NSArray *o_options;
1027     NSURL *o_true_file;
1028
1029     /* Get the item */
1030     o_uri = (NSString *)[o_one_item objectForKey: @"ITEM_URL"];
1031     o_name = (NSString *)[o_one_item objectForKey: @"ITEM_NAME"];
1032     o_options = (NSArray *)[o_one_item objectForKey: @"ITEM_OPTIONS"];
1033
1034     /* Find the name for a disc entry ( i know, can you believe the trouble?) */
1035     if( ( !o_name || [o_name isEqualToString:@""] ) && [o_uri rangeOfString: @"/dev/"].location != NSNotFound )
1036     {
1037         int i_count, i_index;
1038         struct statfs *mounts = NULL;
1039
1040         i_count = getmntinfo (&mounts, MNT_NOWAIT);
1041         /* getmntinfo returns a pointer to static data. Do not free. */
1042         for( i_index = 0 ; i_index < i_count; i_index++ )
1043         {
1044             NSMutableString *o_temp, *o_temp2;
1045             o_temp = [NSMutableString stringWithString: o_uri];
1046             o_temp2 = [NSMutableString stringWithCString: mounts[i_index].f_mntfromname];
1047             [o_temp replaceOccurrencesOfString: @"/dev/rdisk" withString: @"/dev/disk" options:nil range:NSMakeRange(0, [o_temp length]) ];
1048             [o_temp2 replaceOccurrencesOfString: @"s0" withString: @"" options:nil range:NSMakeRange(0, [o_temp2 length]) ];
1049             [o_temp2 replaceOccurrencesOfString: @"s1" withString: @"" options:nil range:NSMakeRange(0, [o_temp2 length]) ];
1050
1051             if( strstr( [o_temp fileSystemRepresentation], [o_temp2 fileSystemRepresentation] ) != NULL )
1052             {
1053                 o_name = [[NSFileManager defaultManager] displayNameAtPath: [NSString stringWithCString:mounts[i_index].f_mntonname]];
1054             }
1055         }
1056     }
1057     /* If no name, then make a guess */
1058     if( !o_name) o_name = [[NSFileManager defaultManager] displayNameAtPath: o_uri];
1059
1060     if( [[NSFileManager defaultManager] fileExistsAtPath:o_uri isDirectory:&b_dir] && b_dir &&
1061         [[NSWorkspace sharedWorkspace] getFileSystemInfoForPath: o_uri isRemovable: &b_rem
1062                 isWritable:NULL isUnmountable:NULL description:NULL type:NULL] && b_rem   )
1063     {
1064         /* All of this is to make sure CD's play when you D&D them on VLC */
1065         /* Converts mountpoint to a /dev file */
1066         struct statfs *buf;
1067         char *psz_dev;
1068         NSMutableString *o_temp;
1069
1070         buf = (struct statfs *) malloc (sizeof(struct statfs));
1071         statfs( [o_uri fileSystemRepresentation], buf );
1072         psz_dev = strdup(buf->f_mntfromname);
1073         o_temp = [NSMutableString stringWithCString: psz_dev ];
1074         [o_temp replaceOccurrencesOfString: @"/dev/disk" withString: @"/dev/rdisk" options:nil range:NSMakeRange(0, [o_temp length]) ];
1075         [o_temp replaceOccurrencesOfString: @"s0" withString: @"" options:nil range:NSMakeRange(0, [o_temp length]) ];
1076         [o_temp replaceOccurrencesOfString: @"s1" withString: @"" options:nil range:NSMakeRange(0, [o_temp length]) ];
1077         o_uri = o_temp;
1078     }
1079
1080     p_item = playlist_ItemNew( p_intf, [o_uri fileSystemRepresentation], [o_name UTF8String] );
1081     if( !p_item )
1082        return NULL;
1083
1084     if( o_options )
1085     {
1086         for( i = 0; i < (int)[o_options count]; i++ )
1087         {
1088             playlist_ItemAddOption( p_item, strdup( [[o_options objectAtIndex:i] UTF8String] ) );
1089         }
1090     }
1091
1092     /* Recent documents menu */
1093     o_true_file = [NSURL fileURLWithPath: o_uri];
1094     if( o_true_file != nil )
1095     {
1096         [[NSDocumentController sharedDocumentController]
1097             noteNewRecentDocumentURL: o_true_file];
1098     }
1099
1100     vlc_object_release( p_playlist );
1101     return p_item;
1102 }
1103
1104 - (void)appendArray:(NSArray*)o_array atPos:(int)i_position enqueue:(BOOL)b_enqueue
1105 {
1106     int i_item;
1107     playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
1108                                             FIND_ANYWHERE );
1109     if( p_playlist == NULL )
1110     {
1111         return;
1112     }
1113
1114     for( i_item = 0; i_item < (int)[o_array count]; i_item++ )
1115     {
1116         playlist_item_t *p_item;
1117         NSDictionary *o_one_item;
1118
1119         /* Get the item */
1120         o_one_item = [o_array objectAtIndex: i_item];
1121         p_item = [self createItem: o_one_item];
1122         if( !p_item )
1123         {
1124             continue;
1125         }
1126
1127         /* Add the item */
1128         playlist_AddItem( p_playlist, p_item, PLAYLIST_INSERT, i_position == -1 ? PLAYLIST_END : i_position + i_item );
1129
1130         if( i_item == 0 && !b_enqueue )
1131         {
1132             playlist_Control( p_playlist, PLAYLIST_ITEMPLAY, p_item );
1133         }
1134     }
1135     vlc_object_release( p_playlist );
1136 }
1137
1138 - (void)appendNodeArray:(NSArray*)o_array inNode:(playlist_item_t *)p_node atPos:(int)i_position inView:(int)i_view enqueue:(BOOL)b_enqueue
1139 {
1140     int i_item;
1141     playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
1142                                             FIND_ANYWHERE );
1143     if( p_playlist == NULL )
1144     {
1145         return;
1146     }
1147
1148     for( i_item = 0; i_item < (int)[o_array count]; i_item++ )
1149     {
1150         playlist_item_t *p_item;
1151         NSDictionary *o_one_item;
1152
1153         /* Get the item */
1154         o_one_item = [o_array objectAtIndex: i_item];
1155         p_item = [self createItem: o_one_item];
1156         if( !p_item )
1157         {
1158             continue;
1159         }
1160
1161         /* Add the item */
1162         playlist_NodeAddItem( p_playlist, p_item, i_view, p_node, PLAYLIST_INSERT, i_position + i_item );
1163
1164         if( i_item == 0 && !b_enqueue )
1165         {
1166             playlist_Control( p_playlist, PLAYLIST_ITEMPLAY, p_item );
1167         }
1168     }
1169     vlc_object_release( p_playlist );
1170
1171 }
1172
1173 - (IBAction)handlePopUp:(id)sender
1174
1175 {
1176     intf_thread_t * p_intf = VLCIntf;
1177     vlc_value_t val1,val2;
1178     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
1179                                             FIND_ANYWHERE );
1180     if( p_playlist == NULL )
1181     {
1182         return;
1183     }
1184
1185     switch( [o_loop_popup indexOfSelectedItem] )
1186     {
1187         case 1:
1188
1189              val1.b_bool = 0;
1190              var_Set( p_playlist, "loop", val1 );
1191              val1.b_bool = 1;
1192              var_Set( p_playlist, "repeat", val1 );
1193              vout_OSDMessage( p_intf, DEFAULT_CHAN, _( "Repeat One" ) );
1194         break;
1195
1196         case 2:
1197              val1.b_bool = 0;
1198              var_Set( p_playlist, "repeat", val1 );
1199              val1.b_bool = 1;
1200              var_Set( p_playlist, "loop", val1 );
1201              vout_OSDMessage( p_intf, DEFAULT_CHAN, _( "Repeat All" ) );
1202         break;
1203
1204         default:
1205              var_Get( p_playlist, "repeat", &val1 );
1206              var_Get( p_playlist, "loop", &val2 );
1207              if( val1.b_bool || val2.b_bool )
1208              {
1209                   val1.b_bool = 0;
1210                   var_Set( p_playlist, "repeat", val1 );
1211                   var_Set( p_playlist, "loop", val1 );
1212                   vout_OSDMessage( p_intf, DEFAULT_CHAN, _( "Repeat Off" ) );
1213              }
1214          break;
1215      }
1216      vlc_object_release( p_playlist );
1217      [self playlistUpdated];
1218 }
1219
1220 - (NSMutableArray *)subSearchItem:(playlist_item_t *)p_item
1221 {
1222     playlist_t *p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
1223                                                        FIND_ANYWHERE );
1224     playlist_item_t *p_selected_item;
1225     int i_current, i_selected_row;
1226
1227     if( !p_playlist )
1228         return NULL;
1229
1230     i_selected_row = [o_outline_view selectedRow];
1231     if (i_selected_row < 0)
1232         i_selected_row = 0;
1233
1234     p_selected_item = (playlist_item_t *)[[o_outline_view itemAtRow:
1235                                             i_selected_row] pointerValue];
1236
1237     for( i_current = 0; i_current < p_item->i_children ; i_current++ )
1238     {
1239         char *psz_temp;
1240         NSString *o_current_name, *o_current_author;
1241
1242         vlc_mutex_lock( &p_playlist->object_lock );
1243         o_current_name = [NSString stringWithUTF8String:
1244             p_item->pp_children[i_current]->input.psz_name];
1245         psz_temp = vlc_input_item_GetInfo( &p_item->input ,
1246                    _("Meta-information"),_("Artist") );
1247         o_current_author = [NSString stringWithUTF8String: psz_temp];
1248         free( psz_temp);
1249         vlc_mutex_unlock( &p_playlist->object_lock );
1250
1251         if( p_selected_item == p_item->pp_children[i_current] &&
1252                     b_selected_item_met == NO )
1253         {
1254             b_selected_item_met = YES;
1255         }
1256         else if( p_selected_item == p_item->pp_children[i_current] &&
1257                     b_selected_item_met == YES )
1258         {
1259             vlc_object_release( p_playlist );
1260             return NULL;
1261         }
1262         else if( b_selected_item_met == YES &&
1263                     ( [o_current_name rangeOfString:[o_search_field
1264                         stringValue] options:NSCaseInsensitiveSearch ].length ||
1265                       [o_current_author rangeOfString:[o_search_field
1266                         stringValue] options:NSCaseInsensitiveSearch ].length ) )
1267         {
1268             vlc_object_release( p_playlist );
1269             /*Adds the parent items in the result array as well, so that we can
1270             expand the tree*/
1271             return [NSMutableArray arrayWithObject: [NSValue
1272                             valueWithPointer: p_item->pp_children[i_current]]];
1273         }
1274         if( p_item->pp_children[i_current]->i_children > 0 )
1275         {
1276             id o_result = [self subSearchItem:
1277                                             p_item->pp_children[i_current]];
1278             if( o_result != NULL )
1279             {
1280                 vlc_object_release( p_playlist );
1281                 [o_result insertObject: [NSValue valueWithPointer:
1282                                 p_item->pp_children[i_current]] atIndex:0];
1283                 return o_result;
1284             }
1285         }
1286     }
1287     vlc_object_release( p_playlist );
1288     return NULL;
1289 }
1290
1291 - (IBAction)searchItem:(id)sender
1292 {
1293     playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
1294                                                        FIND_ANYWHERE );
1295     playlist_view_t * p_view;
1296     id o_result;
1297
1298     unsigned int i;
1299     int i_row = -1;
1300
1301     b_selected_item_met = NO;
1302
1303     if( p_playlist == NULL )
1304         return;
1305     p_view = playlist_ViewFind( p_playlist, i_current_view );
1306
1307     if( p_view )
1308     {
1309         /*First, only search after the selected item:*
1310          *(b_selected_item_met = NO)                 */
1311         o_result = [self subSearchItem:p_view->p_root];
1312         if( o_result == NULL )
1313         {
1314             /* If the first search failed, search again from the beginning */
1315             o_result = [self subSearchItem:p_view->p_root];
1316         }
1317         if( o_result != NULL )
1318         {
1319             int i_start;
1320             if( [[o_result objectAtIndex: 0] pointerValue] ==
1321                                                     p_playlist->p_general )
1322             i_start = 1;
1323             else
1324             i_start = 0;
1325
1326             for( i = i_start ; i < [o_result count] - 1 ; i++ )
1327             {
1328                 [o_outline_view expandItem: [o_outline_dict objectForKey:
1329                             [NSString stringWithFormat: @"%p",
1330                             [[o_result objectAtIndex: i] pointerValue]]]];
1331             }
1332             i_row = [o_outline_view rowForItem: [o_outline_dict objectForKey:
1333                             [NSString stringWithFormat: @"%p",
1334                             [[o_result objectAtIndex: [o_result count] - 1 ]
1335                             pointerValue]]]];
1336         }
1337         if( i_row > -1 )
1338         {
1339             [o_outline_view selectRow:i_row byExtendingSelection: NO];
1340             [o_outline_view scrollRowToVisible: i_row];
1341         }
1342     }
1343     vlc_object_release( p_playlist );
1344 }
1345
1346 - (IBAction)recursiveExpandNode:(id)sender
1347 {
1348     int i;
1349     id o_item = [o_outline_view itemAtRow: [o_outline_view selectedRow]];
1350     playlist_item_t *p_item = (playlist_item_t *)[o_item pointerValue];
1351
1352     if( ![[o_outline_view dataSource] outlineView: o_outline_view
1353                                                     isItemExpandable: o_item] )
1354     {
1355         for( i = 0 ; i < p_item->i_parents ; i++ )
1356         {
1357             if( p_item->pp_parents[i]->i_view == i_current_view )
1358             {
1359                 o_item = [o_outline_dict objectForKey: [NSString
1360                     stringWithFormat: @"%p", p_item->pp_parents[i]->p_parent]];
1361                 break;
1362             }
1363         }
1364     }
1365
1366     /* We need to collapse the node first, since OSX refuses to recursively
1367        expand an already expanded node, even if children nodes are collapsed. */
1368     [o_outline_view collapseItem: o_item collapseChildren: YES];
1369     [o_outline_view expandItem: o_item expandChildren: YES];
1370 }
1371
1372 - (NSMenu *)menuForEvent:(NSEvent *)o_event
1373 {
1374     NSPoint pt;
1375     vlc_bool_t b_rows;
1376     vlc_bool_t b_item_sel;
1377
1378     pt = [o_outline_view convertPoint: [o_event locationInWindow]
1379                                                  fromView: nil];
1380     b_item_sel = ( [o_outline_view rowAtPoint: pt] != -1 &&
1381                    [o_outline_view selectedRow] != -1 );
1382     b_rows = [o_outline_view numberOfRows] != 0;
1383
1384     [o_mi_play setEnabled: b_item_sel];
1385     [o_mi_delete setEnabled: b_item_sel];
1386     [o_mi_selectall setEnabled: b_rows];
1387     [o_mi_info setEnabled: b_item_sel];
1388     [o_mi_preparse setEnabled: b_item_sel];
1389     [o_mi_recursive_expand setEnabled: b_item_sel];
1390     [o_mi_sort_name setEnabled: b_item_sel];
1391     [o_mi_sort_author setEnabled: b_item_sel];
1392
1393     return( o_ctx_menu );
1394 }
1395
1396 - (void)outlineView: (NSTableView*)o_tv
1397                   didClickTableColumn:(NSTableColumn *)o_tc
1398 {
1399     int i_mode = 0, i_type;
1400     intf_thread_t *p_intf = VLCIntf;
1401     playlist_view_t *p_view;
1402
1403     playlist_t *p_playlist = (playlist_t *)vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
1404                                        FIND_ANYWHERE );
1405     if( p_playlist == NULL )
1406     {
1407         return;
1408     }
1409
1410     /* Check whether the selected table column header corresponds to a
1411        sortable table column*/
1412     if( !( o_tc == o_tc_name || o_tc == o_tc_author ) )
1413     {
1414         vlc_object_release( p_playlist );
1415         return;
1416     }
1417
1418     p_view = playlist_ViewFind( p_playlist, i_current_view );
1419
1420     if( o_tc_sortColumn == o_tc )
1421     {
1422         b_isSortDescending = !b_isSortDescending;
1423     }
1424     else
1425     {
1426         b_isSortDescending = VLC_FALSE;
1427     }
1428
1429     if( o_tc == o_tc_name )
1430     {
1431         i_mode = SORT_TITLE;
1432     }
1433     else if( o_tc == o_tc_author )
1434     {
1435         i_mode = SORT_AUTHOR;
1436     }
1437
1438     if( b_isSortDescending )
1439     {
1440         i_type = ORDER_REVERSE;
1441     }
1442     else
1443     {
1444         i_type = ORDER_NORMAL;
1445     }
1446
1447     vlc_mutex_lock( &p_playlist->object_lock );
1448     playlist_RecursiveNodeSort( p_playlist, p_view->p_root, i_mode, i_type );
1449     vlc_mutex_unlock( &p_playlist->object_lock );
1450
1451     vlc_object_release( p_playlist );
1452     [self playlistUpdated];
1453
1454     o_tc_sortColumn = o_tc;
1455     [o_outline_view setHighlightedTableColumn:o_tc];
1456
1457     if( b_isSortDescending )
1458     {
1459         [o_outline_view setIndicatorImage:o_descendingSortingImage
1460                                                         inTableColumn:o_tc];
1461     }
1462     else
1463     {
1464         [o_outline_view setIndicatorImage:o_ascendingSortingImage
1465                                                         inTableColumn:o_tc];
1466     }
1467 }
1468
1469
1470 - (void)outlineView:(NSOutlineView *)outlineView
1471                                 willDisplayCell:(id)cell
1472                                 forTableColumn:(NSTableColumn *)tableColumn
1473                                 item:(id)item
1474 {
1475     playlist_t *p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
1476                                           FIND_ANYWHERE );
1477
1478     id o_playing_item;
1479
1480     if( !p_playlist ) return;
1481
1482     o_playing_item = [o_outline_dict objectForKey:
1483                 [NSString stringWithFormat:@"%p",  p_playlist->status.p_item]];
1484
1485     if( [self isItem: [o_playing_item pointerValue] inNode:
1486                         [item pointerValue] checkItemExistence: YES]
1487                         || [o_playing_item isEqual: item] )
1488     {
1489         [cell setFont: [NSFont boldSystemFontOfSize: 0]];
1490     }
1491     else
1492     {
1493         [cell setFont: [NSFont systemFontOfSize: 0]];
1494     }
1495     vlc_object_release( p_playlist );
1496 }
1497
1498 - (IBAction)addNode:(id)sender
1499 {
1500     /* simply adds a new node to the end of the playlist */
1501     playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
1502                                           FIND_ANYWHERE );
1503     if( !p_playlist )
1504     {
1505         msg_Err( VLCIntf, "Uh Oh! Unable to find playlist!" );
1506         return;
1507     }
1508
1509     playlist_item_t * p_item = playlist_NodeCreate( p_playlist, VIEW_CATEGORY, 
1510         _("Empty Folder"), p_playlist->p_general );
1511
1512     if(! p_item )
1513         msg_Warn( VLCIntf, "node creation failed, fix VLC!" );
1514     
1515     playlist_ViewUpdate( p_playlist, VIEW_CATEGORY );
1516     
1517     vlc_object_release( p_playlist );
1518 }
1519
1520 @end
1521
1522 @implementation VLCPlaylist (NSOutlineViewDataSource)
1523
1524 - (id)outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item
1525 {
1526     id o_value = [super outlineView: outlineView child: index ofItem: item];
1527     playlist_t *p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
1528                                                FIND_ANYWHERE );
1529
1530     if( !p_playlist ) return nil;
1531
1532     if( p_playlist->i_size >= 2 )
1533     {
1534         [o_status_field setStringValue: [NSString stringWithFormat:
1535                     _NS("%i items in the playlist"), p_playlist->i_size]];
1536     }
1537     else
1538     {
1539         if( p_playlist->i_size == 0 )
1540         {
1541             [o_status_field setStringValue: _NS("No items in the playlist")];
1542         }
1543         else
1544         {
1545             [o_status_field setStringValue: _NS("1 item in the playlist")];
1546         }
1547     }
1548     vlc_object_release( p_playlist );
1549
1550     [o_outline_dict setObject:o_value forKey:[NSString stringWithFormat:@"%p",
1551                                                     [o_value pointerValue]]];
1552
1553     return o_value;
1554
1555 }
1556
1557 /* Required for drag & drop and reordering */
1558 - (BOOL)outlineView:(NSOutlineView *)outlineView writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pboard
1559 {
1560     unsigned int i;
1561     playlist_t *p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
1562                                                FIND_ANYWHERE );
1563
1564     /* First remove the items that were moved during the last drag & drop
1565        operation */
1566     [o_items_array removeAllObjects];
1567     [o_nodes_array removeAllObjects];
1568
1569     if( !p_playlist ) return NO;
1570
1571     for( i = 0 ; i < [items count] ; i++ )
1572     {
1573         id o_item = [items objectAtIndex: i];
1574
1575         /* Refuse to move items that are not in the General Node
1576            (Service Discovery) */
1577         if( ![self isItem: [o_item pointerValue] inNode:
1578                         p_playlist->p_general checkItemExistence: NO])
1579         {
1580             vlc_object_release(p_playlist);
1581             return NO;
1582         }
1583         /* Fill the items and nodes to move in 2 different arrays */
1584         if( ((playlist_item_t *)[o_item pointerValue])->i_children > 0 )
1585             [o_nodes_array addObject: o_item];
1586         else
1587             [o_items_array addObject: o_item];
1588     }
1589
1590     /* Now we need to check if there are selected items that are in already
1591        selected nodes. In that case, we only want to move the nodes */
1592     [self removeItemsFrom: o_nodes_array ifChildrenOf: o_nodes_array];
1593     [self removeItemsFrom: o_items_array ifChildrenOf: o_nodes_array];
1594
1595     /* We add the "VLCPlaylistItemPboardType" type to be able to recognize
1596        a Drop operation coming from the playlist. */
1597
1598     [pboard declareTypes: [NSArray arrayWithObjects:
1599         @"VLCPlaylistItemPboardType", nil] owner: self];
1600     [pboard setData:[NSData data] forType:@"VLCPlaylistItemPboardType"];
1601
1602     vlc_object_release(p_playlist);
1603     return YES;
1604 }
1605
1606 - (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id <NSDraggingInfo>)info proposedItem:(id)item proposedChildIndex:(int)index
1607 {
1608     playlist_t *p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
1609                                                FIND_ANYWHERE );
1610     NSPasteboard *o_pasteboard = [info draggingPasteboard];
1611
1612     if( !p_playlist ) return NSDragOperationNone;
1613
1614     /* Dropping ON items is not allowed if item is not a node */
1615     if( item )
1616     {
1617         if( index == NSOutlineViewDropOnItemIndex &&
1618                 ((playlist_item_t *)[item pointerValue])->i_children == -1 )
1619         {
1620             vlc_object_release( p_playlist );
1621             return NSDragOperationNone;
1622         }
1623     }
1624
1625     /* We refuse to drop an item in anything else than a child of the General
1626        Node. We still accept items that would be root nodes of the outlineview
1627        however, to allow drop in an empty playlist. */
1628     if( !([self isItem: [item pointerValue] inNode: p_playlist->p_general
1629                                     checkItemExistence: NO] || item == nil) )
1630     {
1631         vlc_object_release( p_playlist );
1632         return NSDragOperationNone;
1633     }
1634
1635     /* Drop from the Playlist */
1636     if( [[o_pasteboard types] containsObject: @"VLCPlaylistItemPboardType"] )
1637     {
1638         unsigned int i;
1639         for( i = 0 ; i < [o_nodes_array count] ; i++ )
1640         {
1641             /* We refuse to Drop in a child of an item we are moving */
1642             if( [self isItem: [item pointerValue] inNode:
1643                     [[o_nodes_array objectAtIndex: i] pointerValue]
1644                     checkItemExistence: NO] )
1645             {
1646                 vlc_object_release( p_playlist );
1647                 return NSDragOperationNone;
1648             }
1649         }
1650         vlc_object_release(p_playlist);
1651         return NSDragOperationMove;
1652     }
1653
1654     /* Drop from the Finder */
1655     else if( [[o_pasteboard types] containsObject: NSFilenamesPboardType] )
1656     {
1657         vlc_object_release(p_playlist);
1658         return NSDragOperationGeneric;
1659     }
1660     vlc_object_release(p_playlist);
1661     return NSDragOperationNone;
1662 }
1663
1664 - (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id <NSDraggingInfo>)info item:(id)item childIndex:(int)index
1665 {
1666     playlist_t * p_playlist =  vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
1667                                                        FIND_ANYWHERE );
1668     NSPasteboard *o_pasteboard = [info draggingPasteboard];
1669
1670     if( !p_playlist ) return NO;
1671
1672     /* Drag & Drop inside the playlist */
1673     if( [[o_pasteboard types] containsObject: @"VLCPlaylistItemPboardType"] )
1674     {
1675         int i_row, i_removed_from_node = 0;
1676         unsigned int i;
1677         playlist_item_t *p_new_parent, *p_item = NULL;
1678         NSArray *o_all_items = [o_nodes_array arrayByAddingObjectsFromArray:
1679                                                                 o_items_array];
1680         /* If the item is to be dropped as root item of the outline, make it a
1681            child of the General node.
1682            Else, choose the proposed parent as parent. */
1683         if( item == nil ) p_new_parent = p_playlist->p_general;
1684         else p_new_parent = [item pointerValue];
1685
1686         /* Make sure the proposed parent is a node.
1687            (This should never be true) */
1688         if( p_new_parent->i_children < 0 )
1689         {
1690             vlc_object_release( p_playlist );
1691             return NO;
1692         }
1693
1694         for( i = 0; i < [o_all_items count]; i++ )
1695         {
1696             playlist_item_t *p_old_parent = NULL;
1697             int i_old_index = 0;
1698
1699             p_item = [[o_all_items objectAtIndex:i] pointerValue];
1700             p_old_parent = [self parentOfItem: p_item];
1701             if( !p_old_parent )
1702             continue;
1703             /* We may need the old index later */
1704             if( p_new_parent == p_old_parent )
1705             {
1706                 int j;
1707                 for( j = 0; j < p_old_parent->i_children; j++ )
1708                 {
1709                     if( p_old_parent->pp_children[j] == p_item )
1710                     {
1711                         i_old_index = j;
1712                         break;
1713                     }
1714                 }
1715             }
1716
1717             vlc_mutex_lock( &p_playlist->object_lock );
1718             // Acually detach the item from the old position
1719             if( playlist_NodeRemoveItem( p_playlist, p_item, p_old_parent ) ==
1720                 VLC_SUCCESS  &&
1721                 playlist_NodeRemoveParent( p_playlist, p_item, p_old_parent ) ==
1722                 VLC_SUCCESS )
1723             {
1724                 int i_new_index;
1725                 /* Calculate the new index */
1726                 if( index == -1 )
1727                 i_new_index = -1;
1728                 /* If we move the item in the same node, we need to take into
1729                    account that one item will be deleted */
1730                 else
1731                 {
1732                     if ((p_new_parent == p_old_parent &&
1733                                    i_old_index < index + (int)i) )
1734                     {
1735                         i_removed_from_node++;
1736                     }
1737                     i_new_index = index + i - i_removed_from_node;
1738                 }
1739                 // Reattach the item to the new position
1740                 playlist_NodeInsert( p_playlist, i_current_view, p_item,
1741                                                     p_new_parent, i_new_index );
1742             }
1743             vlc_mutex_unlock( &p_playlist->object_lock );
1744         }
1745         [self playlistUpdated];
1746         i_row = [o_outline_view rowForItem:[o_outline_dict
1747             objectForKey:[NSString stringWithFormat: @"%p",
1748             [[o_all_items objectAtIndex: 0] pointerValue]]]];
1749
1750         if( i_row == -1 )
1751         {
1752             i_row = [o_outline_view rowForItem:[o_outline_dict
1753             objectForKey:[NSString stringWithFormat: @"%p", p_new_parent]]];
1754         }
1755
1756         [o_outline_view deselectAll: self];
1757         [o_outline_view selectRow: i_row byExtendingSelection: NO];
1758         [o_outline_view scrollRowToVisible: i_row];
1759
1760         vlc_object_release(p_playlist);
1761         return YES;
1762     }
1763
1764     else if( [[o_pasteboard types] containsObject: NSFilenamesPboardType] )
1765     {
1766         int i;
1767         playlist_item_t *p_node = [item pointerValue];
1768
1769         NSArray *o_array = [NSArray array];
1770         NSArray *o_values = [[o_pasteboard propertyListForType:
1771                                         NSFilenamesPboardType]
1772                                 sortedArrayUsingSelector:
1773                                         @selector(caseInsensitiveCompare:)];
1774
1775         for( i = 0; i < (int)[o_values count]; i++)
1776         {
1777             NSDictionary *o_dic;
1778             o_dic = [NSDictionary dictionaryWithObject:[o_values
1779                         objectAtIndex:i] forKey:@"ITEM_URL"];
1780             o_array = [o_array arrayByAddingObject: o_dic];
1781         }
1782
1783         if ( item == nil )
1784         {
1785             [self appendArray: o_array atPos: index enqueue: YES];
1786         }
1787         /* This should never occur */
1788         else if( p_node->i_children == -1 )
1789         {
1790             vlc_object_release( p_playlist );
1791             return NO;
1792         }
1793         else
1794         {
1795             [self appendNodeArray: o_array inNode: p_node
1796                 atPos: index inView: i_current_view enqueue: YES];
1797         }
1798         vlc_object_release( p_playlist );
1799         return YES;
1800     }
1801     vlc_object_release( p_playlist );
1802     return NO;
1803 }
1804
1805 /* Delegate method of NSWindow */
1806 /*- (void)windowWillClose:(NSNotification *)aNotification
1807 {
1808     [o_btn_playlist setState: NSOffState];
1809 }
1810 */
1811 @end
1812
1813