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