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