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