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