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