]> git.sesse.net Git - vlc/blob - modules/gui/macosx/playlist.m
* Added playlist preparse item to the playlist contextual menu.
[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_preparse setTitle: _NS("Preparse")];
496     [o_mi_sort_name setTitle: _NS("Sort Node by Name")];
497     [o_mi_sort_author setTitle: _NS("Sort Node by Author")];
498     [o_mi_services setTitle: _NS("Services discovery")];
499     [o_status_field setStringValue: [NSString stringWithFormat:
500                         _NS("no items in playlist")]];
501
502     [o_random_ckb setTitle: _NS("Random")];
503 #if 0
504     [o_search_button setTitle: _NS("Search")];
505 #endif
506     [o_search_field setToolTip: _NS("Search in Playlist")];
507     [[o_loop_popup itemAtIndex:0] setTitle: _NS("Standard Play")];
508     [[o_loop_popup itemAtIndex:1] setTitle: _NS("Repeat One")];
509     [[o_loop_popup itemAtIndex:2] setTitle: _NS("Repeat All")];
510 }
511
512 - (void)playlistUpdated
513 {
514     unsigned int i;
515
516     /* Clear indications of any existing column sorting*/
517     for( i = 0 ; i < [[o_outline_view tableColumns] count] ; i++ )
518     {
519         [o_outline_view setIndicatorImage:nil inTableColumn:
520                             [[o_outline_view tableColumns] objectAtIndex:i]];
521     }
522
523     [o_outline_view setHighlightedTableColumn:nil];
524     o_tc_sortColumn = nil;
525     // TODO Find a way to keep the dict size to a minimum
526     //[o_outline_dict removeAllObjects];
527     [o_outline_view reloadData];
528     [[[[VLCMain sharedInstance] getWizard] getPlaylistWizard] reloadOutlineView];
529     [[[[VLCMain sharedInstance] getBookmarks] getDataTable] reloadData];
530 }
531
532 - (void)playModeUpdated
533 {
534     playlist_t *p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
535                                           FIND_ANYWHERE );
536     vlc_value_t val, val2;
537
538     if( p_playlist == NULL )
539     {
540         return;
541     }
542
543     var_Get( p_playlist, "loop", &val2 );
544     var_Get( p_playlist, "repeat", &val );
545     if( val.b_bool == VLC_TRUE )
546     {
547         [o_loop_popup selectItemAtIndex: 1];
548    }
549     else if( val2.b_bool == VLC_TRUE )
550     {
551         [o_loop_popup selectItemAtIndex: 2];
552     }
553     else
554     {
555         [o_loop_popup selectItemAtIndex: 0];
556     }
557
558     var_Get( p_playlist, "random", &val );
559     [o_random_ckb setState: val.b_bool];
560
561     vlc_object_release( p_playlist );
562 }
563
564 - (playlist_item_t *)parentOfItem:(playlist_item_t *)p_item
565 {
566     int i;
567     for( i = 0 ; i < p_item->i_parents; i++ )
568     {
569         if( p_item->pp_parents[i]->i_view == i_current_view )
570         {
571             return p_item->pp_parents[i]->p_parent;
572         }
573     }
574     return NULL;
575 }
576
577 - (void)updateRowSelection
578 {
579 //    int i;
580     int i_row;
581     unsigned int j;
582
583     playlist_t *p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
584                                           FIND_ANYWHERE );
585     playlist_item_t *p_item, *p_temp_item;
586     NSMutableArray *o_array = [NSMutableArray array];
587
588     if( p_playlist == NULL )
589         return;
590
591     p_item = p_playlist->status.p_item;
592     if( p_item == NULL )
593     {
594         vlc_object_release(p_playlist);
595         return;
596     }
597
598     p_temp_item = p_item;
599     while( p_temp_item->i_parents > 0 )
600     {
601         [o_array insertObject: [NSValue valueWithPointer: p_temp_item] atIndex: 0];
602
603         p_temp_item = [self parentOfItem: p_temp_item];
604         /*for (i = 0 ; i < p_temp_item->i_parents ; i++)
605         {
606             if( p_temp_item->pp_parents[i]->i_view == i_current_view )
607             {
608                 p_temp_item = p_temp_item->pp_parents[i]->p_parent;
609                 break;
610             }
611         }*/
612     }
613
614     for (j = 0 ; j < [o_array count] - 1 ; j++)
615     {
616         id o_item;
617         if( ( o_item = [o_outline_dict objectForKey:
618                             [NSString stringWithFormat: @"%p",
619                             [[o_array objectAtIndex:j] pointerValue]]] ) != nil )
620             [o_outline_view expandItem: o_item];
621
622     }
623
624     i_row = [o_outline_view rowForItem:[o_outline_dict
625             objectForKey:[NSString stringWithFormat: @"%p", p_item]]];
626
627     [o_outline_view selectRow: i_row byExtendingSelection: NO];
628     [o_outline_view scrollRowToVisible: i_row];
629
630     vlc_object_release(p_playlist);
631 }
632
633 /* 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
634    deleted. We don't do it when not required  since this verification takes
635    quite a long time on big playlists (yes, pretty hacky). */
636 - (BOOL)isItem: (playlist_item_t *)p_item
637                     inNode: (playlist_item_t *)p_node
638                     checkItemExistence:(BOOL)b_check
639
640 {
641     playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
642                                           FIND_ANYWHERE );
643     playlist_item_t *p_temp_item = p_item;
644
645     if( p_playlist == NULL )
646     {
647         return NO;
648     }
649
650     if( p_node == p_item )
651     {
652         vlc_object_release(p_playlist);
653         return YES;
654     }
655
656     if( p_node->i_children < 1)
657     {
658         vlc_object_release(p_playlist);
659         return NO;
660     }
661
662     if ( p_temp_item )
663     {
664         int i;
665         vlc_mutex_lock( &p_playlist->object_lock );
666
667         if( b_check )
668         {
669         /* Since outlineView: willDisplayCell:... may call this function with
670            p_items that don't exist anymore, first check if the item is still
671            in the playlist. Any cleaner solution welcomed. */
672             for( i = 0; i < p_playlist->i_all_size; i++ )
673             {
674                 if( p_playlist->pp_all_items[i] == p_item ) break;
675                 else if ( i == p_playlist->i_all_size - 1 )
676                 {
677                     vlc_object_release( p_playlist );
678                     vlc_mutex_unlock( &p_playlist->object_lock );
679                     return NO;
680                 }
681             }
682         }
683
684         while( p_temp_item->i_parents > 0 )
685         {
686             p_temp_item = [self parentOfItem: p_temp_item];
687             if( p_temp_item == p_node )
688             {
689                  vlc_mutex_unlock( &p_playlist->object_lock );
690                  vlc_object_release( p_playlist );
691                  return YES;
692             }
693
694 /*            for( i = 0; i < p_temp_item->i_parents ; i++ )
695             {
696                 if( p_temp_item->pp_parents[i]->i_view == i_current_view )
697                 {
698                     if( p_temp_item->pp_parents[i]->p_parent == p_node )
699                     {
700                         vlc_mutex_unlock( &p_playlist->object_lock );
701                         vlc_object_release( p_playlist );
702                         return YES;
703                     }
704                     else
705                     {
706                         p_temp_item = p_temp_item->pp_parents[i]->p_parent;
707                         break;
708                     }
709                 }
710             }*/
711         }
712         vlc_mutex_unlock( &p_playlist->object_lock );
713     }
714
715     vlc_object_release( p_playlist );
716     return NO;
717 }
718
719 /* This method is usefull for instance to remove the selected children of an
720    already selected node */
721 - (void)removeItemsFrom:(id)o_items ifChildrenOf:(id)o_nodes
722 {
723     unsigned int i, j;
724     for( i = 0 ; i < [o_items count] ; i++ )
725     {
726         for ( j = 0 ; j < [o_nodes count] ; j++ )
727         {
728             if( o_items == o_nodes)
729             {
730                 if( j == i ) continue;
731             }
732             if( [self isItem: [[o_items objectAtIndex:i] pointerValue]
733                     inNode: [[o_nodes objectAtIndex:j] pointerValue]
734                     checkItemExistence: NO] )
735             {
736                 [o_items removeObjectAtIndex:i];
737                 /* We need to execute the next iteration with the same index
738                    since the current item has been deleted */
739                 i--;
740                 break;
741             }
742         }
743     }
744
745 }
746
747 - (IBAction)savePlaylist:(id)sender
748 {
749     intf_thread_t * p_intf = VLCIntf;
750     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
751                                                        FIND_ANYWHERE );
752
753     NSSavePanel *o_save_panel = [NSSavePanel savePanel];
754     NSString * o_name = [NSString stringWithFormat: @"%@.m3u", _NS("Untitled")];
755     [o_save_panel setTitle: _NS("Save Playlist")];
756     [o_save_panel setPrompt: _NS("Save")];
757
758     if( [o_save_panel runModalForDirectory: nil
759             file: o_name] == NSOKButton )
760     {
761         playlist_Export( p_playlist, [[o_save_panel filename] fileSystemRepresentation], "export-m3u" );
762     }
763     vlc_object_release( p_playlist );
764 }
765
766
767 /* When called retrieves the selected outlineview row and plays that node or item */
768 - (IBAction)playItem:(id)sender
769 {
770     intf_thread_t * p_intf = VLCIntf;
771     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
772                                                        FIND_ANYWHERE );
773
774     if( p_playlist != NULL )
775     {
776         playlist_item_t *p_item;
777         playlist_item_t *p_node = NULL;
778 //        int i;
779
780         p_item = [[o_outline_view itemAtRow:[o_outline_view selectedRow]] pointerValue];
781
782         if( p_item )
783         {
784             if( p_item->i_children == -1 )
785             {
786                 p_node = [self parentOfItem: p_item];
787
788 /*                for( i = 0 ; i < p_item->i_parents ; i++ )
789                 {
790                     if( p_item->pp_parents[i]->i_view == i_current_view )
791                     {
792                         p_node = p_item->pp_parents[i]->p_parent;
793                     }
794                 }*/
795             }
796             else
797             {
798                 p_node = p_item;
799                 if( p_node->i_children > 0 && p_node->pp_children[0]->i_children == -1 )
800                 {
801                     p_item = p_node->pp_children[0];
802                 }
803                 else
804                 {
805                     p_item = NULL;
806                 }
807             }
808             playlist_Control( p_playlist, PLAYLIST_VIEWPLAY, i_current_view, p_node, p_item );
809         }
810         vlc_object_release( p_playlist );
811     }
812 }
813
814 /* When called retrieves the selected outlineview row and plays that node or item */
815 - (IBAction)preparseItem:(id)sender
816 {
817     int i_count;
818     NSMutableArray *o_to_preparse;
819     intf_thread_t * p_intf = VLCIntf;
820     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
821                                                        FIND_ANYWHERE );
822                                                        
823     o_to_preparse = [NSMutableArray arrayWithArray:[[o_outline_view selectedRowEnumerator] allObjects]];
824     i_count = [o_to_preparse count];
825
826     if( p_playlist != NULL )
827     {
828         int i, i_row;
829         NSNumber *o_number;
830         playlist_item_t *p_item = NULL;
831
832         for( i = 0; i < i_count; i++ )
833         {
834             o_number = [o_to_preparse lastObject];
835             i_row = [o_number intValue];
836             p_item = [[o_outline_view itemAtRow:i_row] pointerValue];
837             [o_to_preparse removeObject: o_number];
838             [o_outline_view deselectRow: i_row];
839
840             if( p_item )
841             {
842                 if( p_item->i_children == -1 )
843                 {
844                     playlist_PreparseEnqueue( p_playlist, &p_item->input );
845                 }
846                 else
847                 {
848                     msg_Dbg( p_intf, "preparse of nodes not yet implemented" );
849                 }
850             }
851         }
852         vlc_object_release( p_playlist );
853     }
854     [self playlistUpdated];
855 }
856
857 - (IBAction)servicesChange:(id)sender
858 {
859     NSMenuItem *o_mi = (NSMenuItem *)sender;
860     NSString *o_string = [o_mi representedObject];
861     playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
862                                           FIND_ANYWHERE );
863     if( !playlist_IsServicesDiscoveryLoaded( p_playlist, [o_string cString] ) )
864         playlist_ServicesDiscoveryAdd( p_playlist, [o_string cString] );
865     else
866         playlist_ServicesDiscoveryRemove( p_playlist, [o_string cString] );
867
868     [o_mi setState: playlist_IsServicesDiscoveryLoaded( p_playlist,
869                                           [o_string cString] ) ? YES : NO];
870
871     i_current_view = VIEW_CATEGORY;
872     playlist_ViewUpdate( p_playlist, i_current_view );
873     vlc_object_release( p_playlist );
874     [self playlistUpdated];
875     return;
876 }
877
878 - (IBAction)selectAll:(id)sender
879 {
880     [o_outline_view selectAll: nil];
881 }
882
883 - (IBAction)deleteItem:(id)sender
884 {
885     int i, i_count, i_row;
886     NSMutableArray *o_to_delete;
887     NSNumber *o_number;
888
889     playlist_t * p_playlist;
890     intf_thread_t * p_intf = VLCIntf;
891
892     p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
893                                           FIND_ANYWHERE );
894
895     if ( p_playlist == NULL )
896     {
897         return;
898     }
899     o_to_delete = [NSMutableArray arrayWithArray:[[o_outline_view selectedRowEnumerator] allObjects]];
900     i_count = [o_to_delete count];
901
902     for( i = 0; i < i_count; i++ )
903     {
904         o_number = [o_to_delete lastObject];
905         i_row = [o_number intValue];
906         id o_item = [o_outline_view itemAtRow: i_row];
907         playlist_item_t *p_item = [o_item pointerValue];
908         [o_to_delete removeObject: o_number];
909         [o_outline_view deselectRow: i_row];
910
911         if( [[o_outline_view dataSource] outlineView:o_outline_view
912                                         numberOfChildrenOfItem: o_item]  > 0 )
913         //is a node and not an item
914         {
915             if( p_playlist->status.i_status != PLAYLIST_STOPPED &&
916                 [self isItem: p_playlist->status.p_item inNode:
917                         ((playlist_item_t *)[o_item pointerValue])
918                         checkItemExistence: NO] == YES )
919             {
920                 // if current item is in selected node and is playing then stop playlist
921                 playlist_Stop( p_playlist );
922             }
923             vlc_mutex_lock( &p_playlist->object_lock );
924             playlist_NodeDelete( p_playlist, p_item, VLC_TRUE, VLC_FALSE );
925             vlc_mutex_unlock( &p_playlist->object_lock );
926         }
927         else
928         {
929             if( p_playlist->status.i_status != PLAYLIST_STOPPED &&
930                 p_playlist->status.p_item == [[o_outline_view itemAtRow: i_row] pointerValue] )
931             {
932                 playlist_Stop( p_playlist );
933             }
934             vlc_mutex_lock( &p_playlist->object_lock );
935             playlist_Delete( p_playlist, p_item->input.i_id );
936             vlc_mutex_unlock( &p_playlist->object_lock );
937         }
938     }
939     [self playlistUpdated];
940     vlc_object_release( p_playlist );
941 }
942
943 - (IBAction)sortNodeByName:(id)sender
944 {
945     [self sortNode: SORT_TITLE];
946 }
947
948 - (IBAction)sortNodeByAuthor:(id)sender
949 {
950     [self sortNode: SORT_AUTHOR];
951 }
952
953 - (void)sortNode:(int)i_mode
954 {
955     playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
956                                           FIND_ANYWHERE );
957     playlist_item_t * p_item;
958
959     if (p_playlist == NULL)
960     {
961         return;
962     }
963
964     if( [o_outline_view selectedRow] > -1 )
965     {
966         p_item = [[o_outline_view itemAtRow: [o_outline_view selectedRow]]
967                                                                 pointerValue];
968     }
969     else
970     /*If no item is selected, sort the whole playlist*/
971     {
972         playlist_view_t * p_view = playlist_ViewFind( p_playlist, i_current_view );
973         p_item = p_view->p_root;
974     }
975
976     if( p_item->i_children > -1 ) // the item is a node
977     {
978         vlc_mutex_lock( &p_playlist->object_lock );
979         playlist_RecursiveNodeSort( p_playlist, p_item, i_mode, ORDER_NORMAL );
980         vlc_mutex_unlock( &p_playlist->object_lock );
981     }
982     else
983     {
984         int i;
985
986         for( i = 0 ; i < p_item->i_parents ; i++ )
987         {
988             if( p_item->pp_parents[i]->i_view == i_current_view )
989             {
990                 vlc_mutex_lock( &p_playlist->object_lock );
991                 playlist_RecursiveNodeSort( p_playlist,
992                         p_item->pp_parents[i]->p_parent, i_mode, ORDER_NORMAL );
993                 vlc_mutex_unlock( &p_playlist->object_lock );
994                 break;
995             }
996         }
997     }
998     vlc_object_release( p_playlist );
999     [self playlistUpdated];
1000 }
1001
1002 - (playlist_item_t *)createItem:(NSDictionary *)o_one_item
1003 {
1004     intf_thread_t * p_intf = VLCIntf;
1005     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
1006                                                        FIND_ANYWHERE );
1007
1008     if( p_playlist == NULL )
1009     {
1010         return NULL;
1011     }
1012     playlist_item_t *p_item;
1013     int i;
1014     BOOL b_rem = FALSE, b_dir = FALSE;
1015     NSString *o_uri, *o_name;
1016     NSArray *o_options;
1017     NSURL *o_true_file;
1018
1019     /* Get the item */
1020     o_uri = (NSString *)[o_one_item objectForKey: @"ITEM_URL"];
1021     o_name = (NSString *)[o_one_item objectForKey: @"ITEM_NAME"];
1022     o_options = (NSArray *)[o_one_item objectForKey: @"ITEM_OPTIONS"];
1023
1024     /* Find the name for a disc entry ( i know, can you believe the trouble?) */
1025     if( ( !o_name || [o_name isEqualToString:@""] ) && [o_uri rangeOfString: @"/dev/"].location != NSNotFound )
1026     {
1027         int i_count, i_index;
1028         struct statfs *mounts = NULL;
1029
1030         i_count = getmntinfo (&mounts, MNT_NOWAIT);
1031         /* getmntinfo returns a pointer to static data. Do not free. */
1032         for( i_index = 0 ; i_index < i_count; i_index++ )
1033         {
1034             NSMutableString *o_temp, *o_temp2;
1035             o_temp = [NSMutableString stringWithString: o_uri];
1036             o_temp2 = [NSMutableString stringWithCString: mounts[i_index].f_mntfromname];
1037             [o_temp replaceOccurrencesOfString: @"/dev/rdisk" withString: @"/dev/disk" options:nil range:NSMakeRange(0, [o_temp length]) ];
1038             [o_temp2 replaceOccurrencesOfString: @"s0" withString: @"" options:nil range:NSMakeRange(0, [o_temp2 length]) ];
1039             [o_temp2 replaceOccurrencesOfString: @"s1" withString: @"" options:nil range:NSMakeRange(0, [o_temp2 length]) ];
1040
1041             if( strstr( [o_temp fileSystemRepresentation], [o_temp2 fileSystemRepresentation] ) != NULL )
1042             {
1043                 o_name = [[NSFileManager defaultManager] displayNameAtPath: [NSString stringWithCString:mounts[i_index].f_mntonname]];
1044             }
1045         }
1046     }
1047     /* If no name, then make a guess */
1048     if( !o_name) o_name = [[NSFileManager defaultManager] displayNameAtPath: o_uri];
1049
1050     if( [[NSFileManager defaultManager] fileExistsAtPath:o_uri isDirectory:&b_dir] && b_dir &&
1051         [[NSWorkspace sharedWorkspace] getFileSystemInfoForPath: o_uri isRemovable: &b_rem
1052                 isWritable:NULL isUnmountable:NULL description:NULL type:NULL] && b_rem   )
1053     {
1054         /* All of this is to make sure CD's play when you D&D them on VLC */
1055         /* Converts mountpoint to a /dev file */
1056         struct statfs *buf;
1057         char *psz_dev;
1058         NSMutableString *o_temp;
1059
1060         buf = (struct statfs *) malloc (sizeof(struct statfs));
1061         statfs( [o_uri fileSystemRepresentation], buf );
1062         psz_dev = strdup(buf->f_mntfromname);
1063         o_temp = [NSMutableString stringWithCString: psz_dev ];
1064         [o_temp replaceOccurrencesOfString: @"/dev/disk" withString: @"/dev/rdisk" options:nil range:NSMakeRange(0, [o_temp length]) ];
1065         [o_temp replaceOccurrencesOfString: @"s0" withString: @"" options:nil range:NSMakeRange(0, [o_temp length]) ];
1066         [o_temp replaceOccurrencesOfString: @"s1" withString: @"" options:nil range:NSMakeRange(0, [o_temp length]) ];
1067         o_uri = o_temp;
1068     }
1069
1070     p_item = playlist_ItemNew( p_intf, [o_uri fileSystemRepresentation], [o_name UTF8String] );
1071     if( !p_item )
1072        return NULL;
1073
1074     if( o_options )
1075     {
1076         for( i = 0; i < (int)[o_options count]; i++ )
1077         {
1078             playlist_ItemAddOption( p_item, strdup( [[o_options objectAtIndex:i] UTF8String] ) );
1079         }
1080     }
1081
1082     /* Recent documents menu */
1083     o_true_file = [NSURL fileURLWithPath: o_uri];
1084     if( o_true_file != nil )
1085     {
1086         [[NSDocumentController sharedDocumentController]
1087             noteNewRecentDocumentURL: o_true_file];
1088     }
1089
1090     vlc_object_release( p_playlist );
1091     return p_item;
1092 }
1093
1094 - (void)appendArray:(NSArray*)o_array atPos:(int)i_position enqueue:(BOOL)b_enqueue
1095 {
1096     int i_item;
1097     playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
1098                                             FIND_ANYWHERE );
1099     if( p_playlist == NULL )
1100     {
1101         return;
1102     }
1103
1104     for( i_item = 0; i_item < (int)[o_array count]; i_item++ )
1105     {
1106         playlist_item_t *p_item;
1107         NSDictionary *o_one_item;
1108
1109         /* Get the item */
1110         o_one_item = [o_array objectAtIndex: i_item];
1111         p_item = [self createItem: o_one_item];
1112         if( !p_item )
1113         {
1114             continue;
1115         }
1116
1117         /* Add the item */
1118         playlist_AddItem( p_playlist, p_item, PLAYLIST_APPEND, i_position == -1 ? PLAYLIST_END : i_position + i_item );
1119
1120         if( i_item == 0 && !b_enqueue )
1121         {
1122             playlist_Control( p_playlist, PLAYLIST_ITEMPLAY, p_item );
1123         }
1124     }
1125     vlc_object_release( p_playlist );
1126 }
1127
1128 - (void)appendNodeArray:(NSArray*)o_array inNode:(playlist_item_t *)p_node atPos:(int)i_position inView:(int)i_view enqueue:(BOOL)b_enqueue
1129 {
1130     int i_item;
1131     playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
1132                                             FIND_ANYWHERE );
1133     if( p_playlist == NULL )
1134     {
1135         return;
1136     }
1137
1138     for( i_item = 0; i_item < (int)[o_array count]; i_item++ )
1139     {
1140         playlist_item_t *p_item;
1141         NSDictionary *o_one_item;
1142
1143         /* Get the item */
1144         o_one_item = [o_array objectAtIndex: i_item];
1145         p_item = [self createItem: o_one_item];
1146         if( !p_item )
1147         {
1148             continue;
1149         }
1150
1151         /* Add the item */
1152         playlist_NodeAddItem( p_playlist, p_item, i_view, p_node, PLAYLIST_APPEND, i_position + i_item );
1153
1154         if( i_item == 0 && !b_enqueue )
1155         {
1156             playlist_Control( p_playlist, PLAYLIST_ITEMPLAY, p_item );
1157         }
1158     }
1159     vlc_object_release( p_playlist );
1160
1161 }
1162
1163 - (IBAction)handlePopUp:(id)sender
1164
1165 {
1166     intf_thread_t * p_intf = VLCIntf;
1167     vlc_value_t val1,val2;
1168     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
1169                                             FIND_ANYWHERE );
1170     if( p_playlist == NULL )
1171     {
1172         return;
1173     }
1174
1175     switch( [o_loop_popup indexOfSelectedItem] )
1176     {
1177         case 1:
1178
1179              val1.b_bool = 0;
1180              var_Set( p_playlist, "loop", val1 );
1181              val1.b_bool = 1;
1182              var_Set( p_playlist, "repeat", val1 );
1183              vout_OSDMessage( p_intf, DEFAULT_CHAN, _( "Repeat One" ) );
1184         break;
1185
1186         case 2:
1187              val1.b_bool = 0;
1188              var_Set( p_playlist, "repeat", val1 );
1189              val1.b_bool = 1;
1190              var_Set( p_playlist, "loop", val1 );
1191              vout_OSDMessage( p_intf, DEFAULT_CHAN, _( "Repeat All" ) );
1192         break;
1193
1194         default:
1195              var_Get( p_playlist, "repeat", &val1 );
1196              var_Get( p_playlist, "loop", &val2 );
1197              if( val1.b_bool || val2.b_bool )
1198              {
1199                   val1.b_bool = 0;
1200                   var_Set( p_playlist, "repeat", val1 );
1201                   var_Set( p_playlist, "loop", val1 );
1202                   vout_OSDMessage( p_intf, DEFAULT_CHAN, _( "Repeat Off" ) );
1203              }
1204          break;
1205      }
1206      vlc_object_release( p_playlist );
1207      [self playlistUpdated];
1208 }
1209
1210 - (NSMutableArray *)subSearchItem:(playlist_item_t *)p_item
1211 {
1212     playlist_t *p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
1213                                                        FIND_ANYWHERE );
1214     playlist_item_t *p_selected_item;
1215     int i_current, i_selected_row;
1216
1217     if( !p_playlist )
1218         return NULL;
1219
1220     i_selected_row = [o_outline_view selectedRow];
1221     if (i_selected_row < 0)
1222         i_selected_row = 0;
1223
1224     p_selected_item = (playlist_item_t *)[[o_outline_view itemAtRow:
1225                                             i_selected_row] pointerValue];
1226
1227     for( i_current = 0; i_current < p_item->i_children ; i_current++ )
1228     {
1229         char *psz_temp;
1230         NSString *o_current_name, *o_current_author;
1231
1232         vlc_mutex_lock( &p_playlist->object_lock );
1233         o_current_name = [NSString stringWithUTF8String:
1234             p_item->pp_children[i_current]->input.psz_name];
1235         psz_temp = vlc_input_item_GetInfo( &p_item->input ,
1236                    _("Meta-information"),_("Artist") );
1237         o_current_author = [NSString stringWithUTF8String: psz_temp];
1238         free( psz_temp);
1239         vlc_mutex_unlock( &p_playlist->object_lock );
1240
1241         if( p_selected_item == p_item->pp_children[i_current] &&
1242                     b_selected_item_met == NO )
1243         {
1244             b_selected_item_met = YES;
1245         }
1246         else if( p_selected_item == p_item->pp_children[i_current] &&
1247                     b_selected_item_met == YES )
1248         {
1249             vlc_object_release( p_playlist );
1250             return NULL;
1251         }
1252         else if( b_selected_item_met == YES &&
1253                     ( [o_current_name rangeOfString:[o_search_field
1254                         stringValue] options:NSCaseInsensitiveSearch ].length ||
1255                       [o_current_author rangeOfString:[o_search_field
1256                         stringValue] options:NSCaseInsensitiveSearch ].length ) )
1257         {
1258             vlc_object_release( p_playlist );
1259             /*Adds the parent items in the result array as well, so that we can
1260             expand the tree*/
1261             return [NSMutableArray arrayWithObject: [NSValue
1262                             valueWithPointer: p_item->pp_children[i_current]]];
1263         }
1264         if( p_item->pp_children[i_current]->i_children > 0 )
1265         {
1266             id o_result = [self subSearchItem:
1267                                             p_item->pp_children[i_current]];
1268             if( o_result != NULL )
1269             {
1270                 vlc_object_release( p_playlist );
1271                 [o_result insertObject: [NSValue valueWithPointer:
1272                                 p_item->pp_children[i_current]] atIndex:0];
1273                 return o_result;
1274             }
1275         }
1276     }
1277     vlc_object_release( p_playlist );
1278     return NULL;
1279 }
1280
1281 - (IBAction)searchItem:(id)sender
1282 {
1283     playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
1284                                                        FIND_ANYWHERE );
1285     playlist_view_t * p_view;
1286     id o_result;
1287
1288     unsigned int i;
1289     int i_row = -1;
1290
1291     b_selected_item_met = NO;
1292
1293     if( p_playlist == NULL )
1294         return;
1295     p_view = playlist_ViewFind( p_playlist, i_current_view );
1296
1297     if( p_view )
1298     {
1299         /*First, only search after the selected item:*
1300          *(b_selected_item_met = NO)                 */
1301         o_result = [self subSearchItem:p_view->p_root];
1302         if( o_result == NULL )
1303         {
1304             /* If the first search failed, search again from the beginning */
1305             o_result = [self subSearchItem:p_view->p_root];
1306         }
1307         if( o_result != NULL )
1308         {
1309             int i_start;
1310             if( [[o_result objectAtIndex: 0] pointerValue] ==
1311                                                     p_playlist->p_general )
1312             i_start = 1;
1313             else
1314             i_start = 0;
1315
1316             for( i = i_start ; i < [o_result count] - 1 ; i++ )
1317             {
1318                 [o_outline_view expandItem: [o_outline_dict objectForKey:
1319                             [NSString stringWithFormat: @"%p",
1320                             [[o_result objectAtIndex: i] pointerValue]]]];
1321             }
1322             i_row = [o_outline_view rowForItem: [o_outline_dict objectForKey:
1323                             [NSString stringWithFormat: @"%p",
1324                             [[o_result objectAtIndex: [o_result count] - 1 ]
1325                             pointerValue]]]];
1326         }
1327         if( i_row > -1 )
1328         {
1329             [o_outline_view selectRow:i_row byExtendingSelection: NO];
1330             [o_outline_view scrollRowToVisible: i_row];
1331         }
1332     }
1333     vlc_object_release( p_playlist );
1334 }
1335
1336 - (IBAction)recursiveExpandNode:(id)sender
1337 {
1338     int i;
1339     id o_item = [o_outline_view itemAtRow: [o_outline_view selectedRow]];
1340     playlist_item_t *p_item = (playlist_item_t *)[o_item pointerValue];
1341
1342     if( ![[o_outline_view dataSource] outlineView: o_outline_view
1343                                                     isItemExpandable: o_item] )
1344     {
1345         for( i = 0 ; i < p_item->i_parents ; i++ )
1346         {
1347             if( p_item->pp_parents[i]->i_view == i_current_view )
1348             {
1349                 o_item = [o_outline_dict objectForKey: [NSString
1350                     stringWithFormat: @"%p", p_item->pp_parents[i]->p_parent]];
1351                 break;
1352             }
1353         }
1354     }
1355
1356     /* We need to collapse the node first, since OSX refuses to recursively
1357        expand an already expanded node, even if children nodes are collapsed. */
1358     [o_outline_view collapseItem: o_item collapseChildren: YES];
1359     [o_outline_view expandItem: o_item expandChildren: YES];
1360 }
1361
1362 - (NSMenu *)menuForEvent:(NSEvent *)o_event
1363 {
1364     NSPoint pt;
1365     vlc_bool_t b_rows;
1366     vlc_bool_t b_item_sel;
1367
1368     pt = [o_outline_view convertPoint: [o_event locationInWindow]
1369                                                  fromView: nil];
1370     b_item_sel = ( [o_outline_view rowAtPoint: pt] != -1 &&
1371                    [o_outline_view selectedRow] != -1 );
1372     b_rows = [o_outline_view numberOfRows] != 0;
1373
1374     [o_mi_play setEnabled: b_item_sel];
1375     [o_mi_delete setEnabled: b_item_sel];
1376     [o_mi_selectall setEnabled: b_rows];
1377     [o_mi_info setEnabled: b_item_sel];
1378     [o_mi_preparse setEnabled: b_item_sel];
1379     [o_mi_recursive_expand setEnabled: b_item_sel];
1380     [o_mi_sort_name setEnabled: b_item_sel];
1381     [o_mi_sort_author setEnabled: b_item_sel];
1382
1383     return( o_ctx_menu );
1384 }
1385
1386 - (void)outlineView: (NSTableView*)o_tv
1387                   didClickTableColumn:(NSTableColumn *)o_tc
1388 {
1389     int i_mode = 0, i_type;
1390     intf_thread_t *p_intf = VLCIntf;
1391     playlist_view_t *p_view;
1392
1393     playlist_t *p_playlist = (playlist_t *)vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
1394                                        FIND_ANYWHERE );
1395     if( p_playlist == NULL )
1396     {
1397         return;
1398     }
1399
1400     /* Check whether the selected table column header corresponds to a
1401        sortable table column*/
1402     if( !( o_tc == o_tc_name || o_tc == o_tc_author ) )
1403     {
1404         vlc_object_release( p_playlist );
1405         return;
1406     }
1407
1408     p_view = playlist_ViewFind( p_playlist, i_current_view );
1409
1410     if( o_tc_sortColumn == o_tc )
1411     {
1412         b_isSortDescending = !b_isSortDescending;
1413     }
1414     else
1415     {
1416         b_isSortDescending = VLC_FALSE;
1417     }
1418
1419     if( o_tc == o_tc_name )
1420     {
1421         i_mode = SORT_TITLE;
1422     }
1423     else if( o_tc == o_tc_author )
1424     {
1425         i_mode = SORT_AUTHOR;
1426     }
1427
1428     if( b_isSortDescending )
1429     {
1430         i_type = ORDER_REVERSE;
1431     }
1432     else
1433     {
1434         i_type = ORDER_NORMAL;
1435     }
1436
1437     vlc_mutex_lock( &p_playlist->object_lock );
1438     playlist_RecursiveNodeSort( p_playlist, p_view->p_root, i_mode, i_type );
1439     vlc_mutex_unlock( &p_playlist->object_lock );
1440
1441     vlc_object_release( p_playlist );
1442     [self playlistUpdated];
1443
1444     o_tc_sortColumn = o_tc;
1445     [o_outline_view setHighlightedTableColumn:o_tc];
1446
1447     if( b_isSortDescending )
1448     {
1449         [o_outline_view setIndicatorImage:o_descendingSortingImage
1450                                                         inTableColumn:o_tc];
1451     }
1452     else
1453     {
1454         [o_outline_view setIndicatorImage:o_ascendingSortingImage
1455                                                         inTableColumn:o_tc];
1456     }
1457 }
1458
1459
1460 - (void)outlineView:(NSOutlineView *)outlineView
1461                                 willDisplayCell:(id)cell
1462                                 forTableColumn:(NSTableColumn *)tableColumn
1463                                 item:(id)item
1464 {
1465     playlist_t *p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
1466                                           FIND_ANYWHERE );
1467
1468     id o_playing_item;
1469
1470     if( !p_playlist ) return;
1471
1472     o_playing_item = [o_outline_dict objectForKey:
1473                 [NSString stringWithFormat:@"%p",  p_playlist->status.p_item]];
1474
1475     if( [self isItem: [o_playing_item pointerValue] inNode:
1476                         [item pointerValue] checkItemExistence: YES]
1477                         || [o_playing_item isEqual: item] )
1478     {
1479         [cell setFont: [NSFont boldSystemFontOfSize: 0]];
1480     }
1481     else
1482     {
1483         [cell setFont: [NSFont systemFontOfSize: 0]];
1484     }
1485     vlc_object_release( p_playlist );
1486 }
1487
1488 @end
1489
1490 @implementation VLCPlaylist (NSOutlineViewDataSource)
1491
1492 - (id)outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item
1493 {
1494     id o_value = [super outlineView: outlineView child: index ofItem: item];
1495     playlist_t *p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
1496                                                FIND_ANYWHERE );
1497
1498     if( !p_playlist ) return nil;
1499
1500     if( p_playlist->i_size >= 2 )
1501     {
1502         [o_status_field setStringValue: [NSString stringWithFormat:
1503                     _NS("%i items in playlist"), p_playlist->i_size]];
1504     }
1505     else
1506     {
1507         if( p_playlist->i_size == 0 )
1508         {
1509             [o_status_field setStringValue: [NSString stringWithFormat:
1510                     _NS("no items in playlist"), p_playlist->i_size]];
1511         }
1512         else
1513         {
1514             [o_status_field setStringValue: [NSString stringWithFormat:
1515                     _NS("1 item in playlist"), p_playlist->i_size]];
1516         }
1517     }
1518     vlc_object_release( p_playlist );
1519
1520     [o_outline_dict setObject:o_value forKey:[NSString stringWithFormat:@"%p",
1521                                                     [o_value pointerValue]]];
1522
1523     return o_value;
1524
1525 }
1526
1527 /* Required for drag & drop and reordering */
1528 - (BOOL)outlineView:(NSOutlineView *)outlineView writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pboard
1529 {
1530     unsigned int i;
1531     playlist_t *p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
1532                                                FIND_ANYWHERE );
1533
1534     /* First remove the items that were moved during the last drag & drop
1535        operation */
1536     [o_items_array removeAllObjects];
1537     [o_nodes_array removeAllObjects];
1538
1539     if( !p_playlist ) return NO;
1540
1541     for( i = 0 ; i < [items count] ; i++ )
1542     {
1543         id o_item = [items objectAtIndex: i];
1544
1545         /* Refuse to move items that are not in the General Node
1546            (Service Discovery) */
1547         if( ![self isItem: [o_item pointerValue] inNode:
1548                         p_playlist->p_general checkItemExistence: NO])
1549         {
1550             vlc_object_release(p_playlist);
1551             return NO;
1552         }
1553         /* Fill the items and nodes to move in 2 different arrays */
1554         if( ((playlist_item_t *)[o_item pointerValue])->i_children > 0 )
1555             [o_nodes_array addObject: o_item];
1556         else
1557             [o_items_array addObject: o_item];
1558     }
1559
1560     /* Now we need to check if there are selected items that are in already
1561        selected nodes. In that case, we only want to move the nodes */
1562     [self removeItemsFrom: o_nodes_array ifChildrenOf: o_nodes_array];
1563     [self removeItemsFrom: o_items_array ifChildrenOf: o_nodes_array];
1564
1565 #if 0
1566     unsigned int j;
1567
1568     for( i = 0 ; i < [o_nodes_array count] ; i++ )
1569     {
1570         for ( j = 0 ; j < [o_nodes_array count] ; j++ )
1571         {
1572             if( j == i ) continue;
1573             if( [self isItem: [[o_nodes_array objectAtIndex:i] pointerValue]
1574                     inNode: [[o_nodes_array objectAtIndex:j] pointerValue]] )
1575             {
1576                 [o_nodes_array removeObjectAtIndex:i];
1577                 /* We need to execute the next iteration with the same index
1578                    since the current item has been deleted */
1579                 i--;
1580                 break;
1581             }
1582         }
1583     }
1584
1585     for( i = 0 ; i < [o_items_array count] ; i++ )
1586     {
1587         for ( j = 0 ; j < [o_nodes_array count] ; j++ )
1588         {
1589             if( [self isItem: [[o_items_array objectAtIndex:i] pointerValue]
1590                     inNode: [[o_nodes_array objectAtIndex:j] pointerValue]] )
1591             {
1592                 [o_items_array removeObjectAtIndex:i];
1593                 i--;
1594                 break;
1595             }
1596         }
1597     }
1598 #endif
1599     /* We add the "VLCPlaylistItemPboardType" type to be able to recognize
1600        a Drop operation comçing from the playlist.
1601        We need to add NSFilenamesPboardType otherwise the outlineview refuses
1602        to the drop. */
1603
1604     [pboard declareTypes: [NSArray arrayWithObjects:
1605         @"VLCPlaylistItemPboardType",NSFilenamesPboardType, nil] owner: self];
1606     [pboard setPropertyList:[NSArray array]
1607                                         forType:NSFilenamesPboardType];
1608
1609     vlc_object_release(p_playlist);
1610     return YES;
1611 }
1612
1613 - (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id <NSDraggingInfo>)info proposedItem:(id)item proposedChildIndex:(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 NSDragOperationNone;
1620
1621     /* We refuse to drop an item in anything else than a child of the General
1622        Node. We still accept items that would be root nodes of the outlineview
1623        however, to allow drop in an empty playlist.*/
1624     if( !([self isItem: [item pointerValue] inNode: p_playlist->p_general
1625                                     checkItemExistence: NO] || item == nil) )
1626     {
1627         vlc_object_release(p_playlist);
1628         return NSDragOperationNone;
1629     }
1630
1631     /* Drop from the Playlist */
1632     if( [[o_pasteboard types] containsObject: @"VLCPlaylistItemPboardType"] )
1633     {
1634         unsigned int i;
1635         for( i = 0 ; i < [o_nodes_array count] ; i++ )
1636         {
1637             /* We refuse to Drop in a child of an item we are moving */
1638             if( [self isItem: [item pointerValue] inNode:
1639                     [[o_nodes_array objectAtIndex: i] pointerValue]
1640                     checkItemExistence: NO] )
1641             {
1642                 vlc_object_release(p_playlist);
1643                 return NSDragOperationNone;
1644             }
1645         }
1646         vlc_object_release(p_playlist);
1647         return NSDragOperationMove;
1648     }
1649
1650     /* Drop from the Finder */
1651     else if( [[o_pasteboard types] containsObject: NSFilenamesPboardType] )
1652     {
1653         vlc_object_release(p_playlist);
1654         return NSDragOperationGeneric;
1655     }
1656     vlc_object_release(p_playlist);
1657     return NSDragOperationNone;
1658 }
1659
1660 - (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id <NSDraggingInfo>)info item:(id)item childIndex:(int)index
1661 {
1662     playlist_t * p_playlist =  vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
1663                                                        FIND_ANYWHERE );
1664     NSPasteboard *o_pasteboard = [info draggingPasteboard];
1665
1666     if( !p_playlist ) return NO;
1667
1668     /* Drag & Drop inside the playlist */
1669     if( [[o_pasteboard types] containsObject: @"VLCPlaylistItemPboardType"] )
1670     {
1671         int i_row;
1672         unsigned int i;
1673         playlist_item_t *p_new_parent, *p_item = NULL;
1674         NSArray *o_all_items = [o_nodes_array arrayByAddingObjectsFromArray:
1675                                                                 o_items_array];
1676         /* If the item is to be dropped as root item of the outline, make it a
1677            child of the General node.
1678            Else, choose the proposed parent as parent. */
1679         if( item == nil )
1680         p_new_parent = p_playlist->p_general;
1681         else
1682         p_new_parent = [item pointerValue];
1683
1684         /* If the proposed parent is not a node, then use the parent node of
1685            this item. */
1686         if( p_new_parent->i_children <= 0 )
1687         {
1688             int j;
1689             playlist_item_t *p_temp_item = p_new_parent;
1690             p_new_parent = [self parentOfItem: p_new_parent];
1691             if( !p_new_parent )
1692             {
1693                 vlc_object_release(p_playlist);
1694                 return NO;
1695             }
1696             /* Calculate the position of the dropped item in this new parent:
1697                following the first proposed parent. */
1698             for( j = 0; j < p_new_parent->i_children; j++ )
1699             {
1700                 if( p_new_parent->pp_children[j] == p_temp_item )
1701                 {
1702                     index = j;
1703                     break;
1704                 }
1705                 else if( j == p_new_parent->i_children - 1 )
1706                 index = -1;
1707             }
1708         }
1709
1710         for( i = 0; i < [o_all_items count]; i++ )
1711         {
1712             playlist_item_t *p_old_parent = NULL;
1713             int i_old_index = 0;
1714
1715             p_item = [[o_all_items objectAtIndex:i] pointerValue];
1716             p_old_parent = [self parentOfItem: p_item];
1717             if( !p_old_parent )
1718             continue;
1719             /* We may need the old index later */
1720             if( p_new_parent == p_old_parent )
1721             {
1722                 int j;
1723                 for( j = 0; j < p_old_parent->i_children; j++ )
1724                 {
1725                     if( p_old_parent->pp_children[j] == p_item )
1726                     {
1727                         i_old_index = j;
1728                         break;
1729                     }
1730                 }
1731             }
1732
1733
1734             /* If we move the playing item in a different node or we move the
1735                node containing the playing item in a different node, then stop
1736                playback, or the playlist refuses to detach the item. */
1737 /*            if( p_playlist->status.i_status != PLAYLIST_STOPPED &&
1738                 (( p_item == p_playlist->status.p_item &&
1739                 p_new_parent != p_old_parent) ||
1740                 ( p_item->i_children > 0 &&
1741                 [self isItem: p_playlist->status.p_item inNode:p_item] == YES))
1742             {
1743                 playlist_Stop( p_playlist );
1744             }*/
1745             vlc_mutex_lock( &p_playlist->object_lock );
1746             // Acually detach the item from the old position
1747             if( playlist_NodeRemoveItem( p_playlist, p_item, p_old_parent ) ==
1748                 VLC_SUCCESS  &&
1749                 playlist_NodeRemoveParent( p_playlist, p_item, p_old_parent ) ==
1750                 VLC_SUCCESS )
1751             {
1752                 int i_new_index;
1753                 /* Calculate the new index */
1754                 if( index == -1 )
1755                 i_new_index = -1;
1756                 /* If we move the item in the same node, we need to take into
1757                    account that one item will be deleted */
1758                 else if((p_new_parent == p_old_parent &&
1759                                                 i_old_index < index + (int)i)
1760                        || p_new_parent == p_playlist->p_general || index == 0 )
1761                 i_new_index = index + i;
1762                 else
1763                 i_new_index = index + i + 1;
1764                 // Reattach the item to the new position
1765                 playlist_NodeInsert( p_playlist, i_current_view, p_item,
1766                                                     p_new_parent, i_new_index );
1767             }
1768             vlc_mutex_unlock( &p_playlist->object_lock );
1769         }
1770         [self playlistUpdated];
1771         i_row = [o_outline_view rowForItem:[o_outline_dict
1772             objectForKey:[NSString stringWithFormat: @"%p",
1773             [[o_all_items objectAtIndex: 0] pointerValue]]]];
1774
1775         if( i_row == -1 )
1776         {
1777             i_row = [o_outline_view rowForItem:[o_outline_dict
1778             objectForKey:[NSString stringWithFormat: @"%p", p_new_parent]]];
1779         }
1780
1781         [o_outline_view deselectAll: self];
1782         [o_outline_view selectRow: i_row byExtendingSelection: NO];
1783         [o_outline_view scrollRowToVisible: i_row];
1784
1785         vlc_object_release(p_playlist);
1786         return YES;
1787     }
1788
1789     else if( [[o_pasteboard types] containsObject: NSFilenamesPboardType] )
1790     {
1791         int i;
1792         playlist_item_t *p_node = [item pointerValue];
1793
1794         NSArray *o_array = [NSArray array];
1795         NSArray *o_values = [[o_pasteboard propertyListForType:
1796                                         NSFilenamesPboardType]
1797                                 sortedArrayUsingSelector:
1798                                         @selector(caseInsensitiveCompare:)];
1799
1800         for( i = 0; i < (int)[o_values count]; i++)
1801         {
1802             NSDictionary *o_dic;
1803             o_dic = [NSDictionary dictionaryWithObject:[o_values
1804                         objectAtIndex:i] forKey:@"ITEM_URL"];
1805             o_array = [o_array arrayByAddingObject: o_dic];
1806         }
1807
1808         if ( item == nil )
1809         {
1810             [self appendArray: o_array atPos: index enqueue: YES];
1811         }
1812         else if( p_node->i_children == -1 )
1813         {
1814             int i_counter;
1815             playlist_item_t *p_real_node = NULL;
1816
1817             for( i_counter = 0 ; i_counter < p_node->i_parents ; i_counter++ )
1818             {
1819                 if( p_node->pp_parents[i_counter]->i_view == i_current_view )
1820                 {
1821                     p_real_node = p_node->pp_parents[i_counter]->p_parent;
1822                     break;
1823                 }
1824                 if( i_counter == p_node->i_parents )
1825                 {
1826                     vlc_object_release(p_playlist);
1827                     return NO;
1828                 }
1829             }
1830             [self appendNodeArray: o_array inNode: p_real_node
1831                 atPos: index inView: i_current_view enqueue: YES];
1832         }
1833         else
1834         {
1835             [self appendNodeArray: o_array inNode: p_node
1836                 atPos: index inView: i_current_view enqueue: YES];
1837         }
1838         vlc_object_release( p_playlist );
1839         return YES;
1840     }
1841     vlc_object_release( p_playlist );
1842     return NO;
1843 }
1844
1845 /* Delegate method of NSWindow */
1846 /*- (void)windowWillClose:(NSNotification *)aNotification
1847 {
1848     [o_btn_playlist setState: NSOffState];
1849 }
1850 */
1851 @end
1852
1853