]> git.sesse.net Git - vlc/blob - modules/gui/macosx/playlist.m
FSF address change.
[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 }
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 the playlist"), p_playlist->i_size]];
1513     }
1514     else
1515     {
1516         if( p_playlist->i_size == 0 )
1517         {
1518             [o_status_field setStringValue: _NS("No items in the playlist")];
1519         }
1520         else
1521         {
1522             [o_status_field setStringValue: _NS("1 item in the playlist")];
1523         }
1524     }
1525     vlc_object_release( p_playlist );
1526
1527     [o_outline_dict setObject:o_value forKey:[NSString stringWithFormat:@"%p",
1528                                                     [o_value pointerValue]]];
1529
1530     return o_value;
1531
1532 }
1533
1534 /* Required for drag & drop and reordering */
1535 - (BOOL)outlineView:(NSOutlineView *)outlineView writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pboard
1536 {
1537     unsigned int i;
1538     playlist_t *p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
1539                                                FIND_ANYWHERE );
1540
1541     /* First remove the items that were moved during the last drag & drop
1542        operation */
1543     [o_items_array removeAllObjects];
1544     [o_nodes_array removeAllObjects];
1545
1546     if( !p_playlist ) return NO;
1547
1548     for( i = 0 ; i < [items count] ; i++ )
1549     {
1550         id o_item = [items objectAtIndex: i];
1551
1552         /* Refuse to move items that are not in the General Node
1553            (Service Discovery) */
1554         if( ![self isItem: [o_item pointerValue] inNode:
1555                         p_playlist->p_general checkItemExistence: NO])
1556         {
1557             vlc_object_release(p_playlist);
1558             return NO;
1559         }
1560         /* Fill the items and nodes to move in 2 different arrays */
1561         if( ((playlist_item_t *)[o_item pointerValue])->i_children > 0 )
1562             [o_nodes_array addObject: o_item];
1563         else
1564             [o_items_array addObject: o_item];
1565     }
1566
1567     /* Now we need to check if there are selected items that are in already
1568        selected nodes. In that case, we only want to move the nodes */
1569     [self removeItemsFrom: o_nodes_array ifChildrenOf: o_nodes_array];
1570     [self removeItemsFrom: o_items_array ifChildrenOf: o_nodes_array];
1571
1572     /* We add the "VLCPlaylistItemPboardType" type to be able to recognize
1573        a Drop operation coming from the playlist. */
1574
1575     [pboard declareTypes: [NSArray arrayWithObjects:
1576         @"VLCPlaylistItemPboardType", nil] owner: self];
1577     [pboard setData:[NSData data] forType:@"VLCPlaylistItemPboardType"];
1578
1579     vlc_object_release(p_playlist);
1580     return YES;
1581 }
1582
1583 - (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id <NSDraggingInfo>)info proposedItem:(id)item proposedChildIndex:(int)index
1584 {
1585     playlist_t *p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
1586                                                FIND_ANYWHERE );
1587     NSPasteboard *o_pasteboard = [info draggingPasteboard];
1588
1589     if( !p_playlist ) return NSDragOperationNone;
1590
1591     /* Dropping ON items is not allowed if item is not a node */
1592     if( item )
1593     {
1594         if( index == NSOutlineViewDropOnItemIndex &&
1595                 ((playlist_item_t *)[item pointerValue])->i_children == -1 )
1596         {
1597             vlc_object_release( p_playlist );
1598             return NSDragOperationNone;
1599         }
1600     }
1601
1602     /* We refuse to drop an item in anything else than a child of the General
1603        Node. We still accept items that would be root nodes of the outlineview
1604        however, to allow drop in an empty playlist. */
1605     if( !([self isItem: [item pointerValue] inNode: p_playlist->p_general
1606                                     checkItemExistence: NO] || item == nil) )
1607     {
1608         vlc_object_release( p_playlist );
1609         return NSDragOperationNone;
1610     }
1611
1612     /* Drop from the Playlist */
1613     if( [[o_pasteboard types] containsObject: @"VLCPlaylistItemPboardType"] )
1614     {
1615         unsigned int i;
1616         for( i = 0 ; i < [o_nodes_array count] ; i++ )
1617         {
1618             /* We refuse to Drop in a child of an item we are moving */
1619             if( [self isItem: [item pointerValue] inNode:
1620                     [[o_nodes_array objectAtIndex: i] pointerValue]
1621                     checkItemExistence: NO] )
1622             {
1623                 vlc_object_release( p_playlist );
1624                 return NSDragOperationNone;
1625             }
1626         }
1627         vlc_object_release(p_playlist);
1628         return NSDragOperationMove;
1629     }
1630
1631     /* Drop from the Finder */
1632     else if( [[o_pasteboard types] containsObject: NSFilenamesPboardType] )
1633     {
1634         vlc_object_release(p_playlist);
1635         return NSDragOperationGeneric;
1636     }
1637     vlc_object_release(p_playlist);
1638     return NSDragOperationNone;
1639 }
1640
1641 - (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id <NSDraggingInfo>)info item:(id)item childIndex:(int)index
1642 {
1643     playlist_t * p_playlist =  vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
1644                                                        FIND_ANYWHERE );
1645     NSPasteboard *o_pasteboard = [info draggingPasteboard];
1646
1647     if( !p_playlist ) return NO;
1648
1649     /* Drag & Drop inside the playlist */
1650     if( [[o_pasteboard types] containsObject: @"VLCPlaylistItemPboardType"] )
1651     {
1652         int i_row, i_removed_from_node = 0;
1653         unsigned int i;
1654         playlist_item_t *p_new_parent, *p_item = NULL;
1655         NSArray *o_all_items = [o_nodes_array arrayByAddingObjectsFromArray:
1656                                                                 o_items_array];
1657         /* If the item is to be dropped as root item of the outline, make it a
1658            child of the General node.
1659            Else, choose the proposed parent as parent. */
1660         if( item == nil ) p_new_parent = p_playlist->p_general;
1661         else p_new_parent = [item pointerValue];
1662
1663         /* Make sure the proposed parent is a node.
1664            (This should never be true) */
1665         if( p_new_parent->i_children < 0 )
1666         {
1667             vlc_object_release( p_playlist );
1668             return NO;
1669         }
1670
1671         for( i = 0; i < [o_all_items count]; i++ )
1672         {
1673             playlist_item_t *p_old_parent = NULL;
1674             int i_old_index = 0;
1675
1676             p_item = [[o_all_items objectAtIndex:i] pointerValue];
1677             p_old_parent = [self parentOfItem: p_item];
1678             if( !p_old_parent )
1679             continue;
1680             /* We may need the old index later */
1681             if( p_new_parent == p_old_parent )
1682             {
1683                 int j;
1684                 for( j = 0; j < p_old_parent->i_children; j++ )
1685                 {
1686                     if( p_old_parent->pp_children[j] == p_item )
1687                     {
1688                         i_old_index = j;
1689                         break;
1690                     }
1691                 }
1692             }
1693
1694             vlc_mutex_lock( &p_playlist->object_lock );
1695             // Acually detach the item from the old position
1696             if( playlist_NodeRemoveItem( p_playlist, p_item, p_old_parent ) ==
1697                 VLC_SUCCESS  &&
1698                 playlist_NodeRemoveParent( p_playlist, p_item, p_old_parent ) ==
1699                 VLC_SUCCESS )
1700             {
1701                 int i_new_index;
1702                 /* Calculate the new index */
1703                 if( index == -1 )
1704                 i_new_index = -1;
1705                 /* If we move the item in the same node, we need to take into
1706                    account that one item will be deleted */
1707                 else
1708                 {
1709                     if ((p_new_parent == p_old_parent &&
1710                                    i_old_index < index + (int)i) )
1711                     {
1712                         i_removed_from_node++;
1713                     }
1714                     i_new_index = index + i - i_removed_from_node;
1715                 }
1716                 // Reattach the item to the new position
1717                 playlist_NodeInsert( p_playlist, i_current_view, p_item,
1718                                                     p_new_parent, i_new_index );
1719             }
1720             vlc_mutex_unlock( &p_playlist->object_lock );
1721         }
1722         [self playlistUpdated];
1723         i_row = [o_outline_view rowForItem:[o_outline_dict
1724             objectForKey:[NSString stringWithFormat: @"%p",
1725             [[o_all_items objectAtIndex: 0] pointerValue]]]];
1726
1727         if( i_row == -1 )
1728         {
1729             i_row = [o_outline_view rowForItem:[o_outline_dict
1730             objectForKey:[NSString stringWithFormat: @"%p", p_new_parent]]];
1731         }
1732
1733         [o_outline_view deselectAll: self];
1734         [o_outline_view selectRow: i_row byExtendingSelection: NO];
1735         [o_outline_view scrollRowToVisible: i_row];
1736
1737         vlc_object_release(p_playlist);
1738         return YES;
1739     }
1740
1741     else if( [[o_pasteboard types] containsObject: NSFilenamesPboardType] )
1742     {
1743         int i;
1744         playlist_item_t *p_node = [item pointerValue];
1745
1746         NSArray *o_array = [NSArray array];
1747         NSArray *o_values = [[o_pasteboard propertyListForType:
1748                                         NSFilenamesPboardType]
1749                                 sortedArrayUsingSelector:
1750                                         @selector(caseInsensitiveCompare:)];
1751
1752         for( i = 0; i < (int)[o_values count]; i++)
1753         {
1754             NSDictionary *o_dic;
1755             o_dic = [NSDictionary dictionaryWithObject:[o_values
1756                         objectAtIndex:i] forKey:@"ITEM_URL"];
1757             o_array = [o_array arrayByAddingObject: o_dic];
1758         }
1759
1760         if ( item == nil )
1761         {
1762             [self appendArray: o_array atPos: index enqueue: YES];
1763         }
1764         /* This should never occur */
1765         else if( p_node->i_children == -1 )
1766         {
1767             vlc_object_release( p_playlist );
1768             return NO;
1769         }
1770         else
1771         {
1772             [self appendNodeArray: o_array inNode: p_node
1773                 atPos: index inView: i_current_view enqueue: YES];
1774         }
1775         vlc_object_release( p_playlist );
1776         return YES;
1777     }
1778     vlc_object_release( p_playlist );
1779     return NO;
1780 }
1781
1782 /* Delegate method of NSWindow */
1783 /*- (void)windowWillClose:(NSNotification *)aNotification
1784 {
1785     [o_btn_playlist setState: NSOffState];
1786 }
1787 */
1788 @end
1789
1790