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