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