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