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