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