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