services_discovery: implement SD categories and use in Qt interface
[vlc.git] / modules / gui / macosx / playlist.m
blobba929645f33025bbca4b9edeebc42d7f899c9c2b
1 /*****************************************************************************
2  * playlist.m: MacOS X interface module
3  *****************************************************************************
4 * Copyright (C) 2002-2009 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  *****************************************************************************/
26 /* TODO
27  * add 'icons' for different types of nodes? (http://www.cocoadev.com/index.pl?IconAndTextInTableCell)
28  * reimplement enable/disable item
29  * create a new 'tool' button (see the gear button in the Finder window) for 'actions'
30    (adding service discovery, other views, new node/playlist, save node/playlist) stuff like that
31  */
34 /*****************************************************************************
35  * Preamble
36  *****************************************************************************/
37 #include <stdlib.h>                                      /* malloc(), free() */
38 #include <sys/param.h>                                    /* for MAXPATHLEN */
39 #include <string.h>
40 #include <math.h>
41 #include <sys/mount.h>
43 #import "intf.h"
44 #import "wizard.h"
45 #import "bookmarks.h"
46 #import "playlistinfo.h"
47 #import "playlist.h"
48 #import "controls.h"
49 #import "misc.h"
50 #import "sidebarview.h"
52 #include <vlc_keys.h>
53 #import <vlc_services_discovery.h>
54 #import <vlc_osd.h>
55 #import <vlc_interface.h>
58 /*****************************************************************************
59  * VLCPlaylistView implementation
60  *****************************************************************************/
61 @implementation VLCPlaylistView
63 - (NSMenu *)menuForEvent:(NSEvent *)o_event
65     return( [[self delegate] menuForEvent: o_event] );
68 - (void)keyDown:(NSEvent *)o_event
70     unichar key = 0;
72     if( [[o_event characters] length] )
73     {
74         key = [[o_event characters] characterAtIndex: 0];
75     }
77     switch( key )
78     {
79         case NSDeleteCharacter:
80         case NSDeleteFunctionKey:
81         case NSDeleteCharFunctionKey:
82         case NSBackspaceCharacter:
83             [[self delegate] deleteItem:self];
84             break;
86         case NSEnterCharacter:
87         case NSCarriageReturnCharacter:
88             [(VLCPlaylist *)[[VLCMain sharedInstance] playlist] playItem:self];
89             break;
91         default:
92             [super keyDown: o_event];
93             break;
94     }
97 @end
99 /*****************************************************************************
100  * VLCPlaylistCommon implementation
102  * This class the superclass of the VLCPlaylist and VLCPlaylistWizard.
103  * It contains the common methods and elements of these 2 entities.
104  *****************************************************************************/
105 @implementation VLCPlaylistCommon
107 - (id)init
109     self = [super init];
110     if ( self != nil )
111     {
112         o_outline_dict = [[NSMutableDictionary alloc] init];
113     }
114     return self;
116 - (void)awakeFromNib
118     playlist_t * p_playlist = pl_Get( VLCIntf );
119     [o_outline_view setTarget: self];
120     [o_outline_view setDelegate: self];
121     [o_outline_view setDataSource: self];
122     [o_outline_view setAllowsEmptySelection: NO];
123     [o_outline_view expandItem: [o_outline_view itemAtRow:0]];
125         [o_outline_view_other setTarget: self];
126     [o_outline_view_other setDelegate: self];
127     [o_outline_view_other setDataSource: self];
128     [o_outline_view_other setAllowsEmptySelection: NO];
130     [self initStrings];
133 - (void)initStrings
135     [[o_tc_name headerCell] setStringValue:_NS("Name")];
136     [[o_tc_author headerCell] setStringValue:_NS("Author")];
137     [[o_tc_duration headerCell] setStringValue:_NS("Duration")];
139         [[o_tc_name_other headerCell] setStringValue:_NS("Name")];
140     [[o_tc_author_other headerCell] setStringValue:_NS("Author")];
141     [[o_tc_duration_other headerCell] setStringValue:_NS("Duration")];
144 - (void)swapPlaylists:(id)newList
146         if(newList != o_outline_view)
147         {
148                 id o_outline_view_temp = o_outline_view;
149                 id o_tc_author_temp = o_tc_author;
150                 id o_tc_duration_temp = o_tc_duration;
151                 id o_tc_name_temp = o_tc_name;
152                 o_outline_view = o_outline_view_other;
153                 o_tc_author = o_tc_author_other;
154                 o_tc_duration = o_tc_duration_other;
155                 o_tc_name = o_tc_name_other;
156                 o_outline_view_other = o_outline_view_temp;
157                 o_tc_author_other = o_tc_author_temp;
158                 o_tc_duration_other = o_tc_duration_temp;
159                 o_tc_name_other = o_tc_name_temp;
160         }
163 - (NSOutlineView *)outlineView
165     return o_outline_view;
168 - (playlist_item_t *)selectedPlaylistItem
170     return [[o_outline_view itemAtRow: [o_outline_view selectedRow]]
171                                                                 pointerValue];
174 @end
176 @implementation VLCPlaylistCommon (NSOutlineViewDataSource)
178 /* return the number of children for Obj-C pointer item */ /* DONE */
179 - (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
181     int i_return = 0;
182     playlist_item_t *p_item = NULL;
183     playlist_t * p_playlist = pl_Get( VLCIntf );
184     //assert( outlineView == o_outline_view );
186     if( !item )
187         p_item = p_playlist->p_root_category;
188     else
189         p_item = (playlist_item_t *)[item pointerValue];
191     if( p_item )
192         i_return = p_item->i_children;
194     return i_return > 0 ? i_return : 0;
197 /* return the child at index for the Obj-C pointer item */ /* DONE */
198 - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item
200     playlist_item_t *p_return = NULL, *p_item = NULL;
201     NSValue *o_value;
202     playlist_t * p_playlist = pl_Get( VLCIntf );
204     PL_LOCK;
205     if( item == nil )
206     {
207         /* root object */
208         p_item = p_playlist->p_root_category;
209     }
210     else
211     {
212         p_item = (playlist_item_t *)[item pointerValue];
213     }
214     if( p_item && index < p_item->i_children && index >= 0 )
215         p_return = p_item->pp_children[index];
216     PL_UNLOCK;
218     o_value = [o_outline_dict objectForKey:[NSString stringWithFormat: @"%p", p_return]];
220     if( o_value == nil )
221     {
222         /* FIXME: Why is there a warning if that happens all the time and seems
223          * to be normal? Add an assert and fix it. 
224          * msg_Warn( VLCIntf, "playlist item misses pointer value, adding one" ); */
225         o_value = [[NSValue valueWithPointer: p_return] retain];
226     }
227     return o_value;
230 /* is the item expandable */
231 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
233     int i_return = 0;
234     playlist_t *p_playlist = pl_Get( VLCIntf );
236     PL_LOCK;
237     if( item == nil )
238     {
239         /* root object */
240         if( p_playlist->p_root_category )
241         {
242             i_return = p_playlist->p_root_category->i_children;
243         }
244     }
245     else
246     {
247         playlist_item_t *p_item = (playlist_item_t *)[item pointerValue];
248         if( p_item )
249             i_return = p_item->i_children;
250     }
251     PL_UNLOCK;
253     return (i_return >= 0);
256 /* retrieve the string values for the cells */
257 - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)o_tc byItem:(id)item
259     id o_value = nil;
260     playlist_item_t *p_item;
262     /* For error handling */
263     static BOOL attempted_reload = NO;
265     if( item == nil || ![item isKindOfClass: [NSValue class]] )
266     {
267         /* Attempt to fix the error by asking for a data redisplay
268          * This might cause infinite loop, so add a small check */
269         if( !attempted_reload )
270         {
271             attempted_reload = YES;
272             [outlineView reloadData];
273         }
274         return @"error" ;
275     }
277     p_item = (playlist_item_t *)[item pointerValue];
278     if( !p_item || !p_item->p_input )
279     {
280         /* Attempt to fix the error by asking for a data redisplay
281          * This might cause infinite loop, so add a small check */
282         if( !attempted_reload )
283         {
284             attempted_reload = YES;
285             [outlineView reloadData];
286         }
287         return @"error";
288     }
290     attempted_reload = NO;
292     if( [[o_tc identifier] isEqualToString:@"name"] )
293     {
294         /* sanity check to prevent the NSString class from crashing */
295         char *psz_title =  input_item_GetTitleFbName( p_item->p_input );
296         if( psz_title )
297         {
298             o_value = [NSString stringWithUTF8String: psz_title];
299             free( psz_title );
300         }
301     }
302     else if( [[o_tc identifier] isEqualToString:@"artist"] )
303     {
304         char *psz_artist = input_item_GetArtist( p_item->p_input );
305         if( psz_artist )
306             o_value = [NSString stringWithUTF8String: psz_artist];
307         free( psz_artist );
308     }
309     else if( [[o_tc identifier] isEqualToString:@"duration"] )
310     {
311         char psz_duration[MSTRTIME_MAX_SIZE];
312         mtime_t dur = input_item_GetDuration( p_item->p_input );
313         if( dur != -1 )
314         {
315             secstotimestr( psz_duration, dur/1000000 );
316             o_value = [NSString stringWithUTF8String: psz_duration];
317         }
318         else
319             o_value = @"--:--";
320     }
321     else if( [[o_tc identifier] isEqualToString:@"status"] )
322     {
323         if( input_item_HasErrorWhenReading( p_item->p_input ) )
324         {
325             o_value = [NSImage imageWithWarningIcon];
326         }
327     }
328     return o_value;
331 @end
333 /*****************************************************************************
334  * VLCPlaylistWizard implementation
335  *****************************************************************************/
336 @implementation VLCPlaylistWizard
338 - (IBAction)reloadOutlineView
340     /* Only reload the outlineview if the wizard window is open since this can
341        be quite long on big playlists */
342     if( [[o_outline_view window] isVisible] )
343     {
344         [o_outline_view reloadData];
345     }
348 @end
350 /*****************************************************************************
351  * extension to NSOutlineView's interface to fix compilation warnings
352  * and let us access these 2 functions properly
353  * this uses a private Apple-API, but works fine on all current OSX releases
354  * keep checking for compatiblity with future releases though
355  *****************************************************************************/
357 @interface NSOutlineView (UndocumentedSortImages)
358 + (NSImage *)_defaultTableHeaderSortImage;
359 + (NSImage *)_defaultTableHeaderReverseSortImage;
360 @end
363 /*****************************************************************************
364  * VLCPlaylist implementation
365  *****************************************************************************/
366 @implementation VLCPlaylist
368 - (id)init
370     self = [super init];
371     if ( self != nil )
372     {
373         o_nodes_array = [[NSMutableArray alloc] init];
374         o_items_array = [[NSMutableArray alloc] init];
375     }
376     return self;
379 - (void)dealloc
381     [o_nodes_array release];
382     [o_items_array release];
383     [super dealloc];
386 - (void)awakeFromNib
388     playlist_t * p_playlist = pl_Get( VLCIntf );
390     int i;
392     [super awakeFromNib];
394     [o_outline_view setDoubleAction: @selector(playItem:)];
395     [o_outline_view_other setDoubleAction: @selector(playItem:)];
397     [o_outline_view registerForDraggedTypes:
398         [NSArray arrayWithObjects: NSFilenamesPboardType,
399         @"VLCPlaylistItemPboardType", nil]];
400     [o_outline_view setIntercellSpacing: NSMakeSize (0.0, 1.0)];
402     [o_outline_view_other registerForDraggedTypes:
403      [NSArray arrayWithObjects: NSFilenamesPboardType,
404       @"VLCPlaylistItemPboardType", nil]];
405     [o_outline_view_other setIntercellSpacing: NSMakeSize (0.0, 1.0)];
407     /* This uses private Apple API which works fine until 10.5.
408      * We need to keep checking in the future!
409      * These methods are being added artificially to NSOutlineView's interface above */
410     o_ascendingSortingImage = [[NSOutlineView class] _defaultTableHeaderSortImage];
411     o_descendingSortingImage = [[NSOutlineView class] _defaultTableHeaderReverseSortImage];
413     o_tc_sortColumn = nil;
415     char ** ppsz_name;
416     char ** ppsz_services = vlc_sd_GetNames( VLCIntf, &ppsz_name, NULL );
417     if( !ppsz_services )
418         return;
419     
420     for( i = 0; ppsz_services[i]; i++ )
421     {
422         bool  b_enabled;
423         NSMenuItem  *o_lmi;
425         char * name = ppsz_name[i] ? ppsz_name[i] : ppsz_services[i];
426         /* Check whether to enable these menuitems */
427         b_enabled = playlist_IsServicesDiscoveryLoaded( p_playlist, ppsz_services[i] );
429         /* Create the menu entries used in the playlist menu */
430         o_lmi = [[o_mi_services submenu] addItemWithTitle:
431                  [NSString stringWithUTF8String: name]
432                                          action: @selector(servicesChange:)
433                                          keyEquivalent: @""];
434         [o_lmi setTarget: self];
435         [o_lmi setRepresentedObject: [NSString stringWithUTF8String: ppsz_services[i]]];
436         if( b_enabled ) [o_lmi setState: NSOnState];
438         /* Create the menu entries for the main menu */
439         o_lmi = [[o_mm_mi_services submenu] addItemWithTitle:
440                  [NSString stringWithUTF8String: name]
441                                          action: @selector(servicesChange:)
442                                          keyEquivalent: @""];
443         [o_lmi setTarget: self];
444         [o_lmi setRepresentedObject: [NSString stringWithUTF8String: ppsz_services[i]]];
445         if( b_enabled ) [o_lmi setState: NSOnState];
447         free( ppsz_services[i] );
448         free( ppsz_name[i] );
449     }
450     free( ppsz_services );
451     free( ppsz_name );
454 - (void)searchfieldChanged:(NSNotification *)o_notification
456     [o_search_field setStringValue:[[o_notification object] stringValue]];
459 - (void)initStrings
461     [super initStrings];
463     [o_mi_save_playlist setTitle: _NS("Save Playlist...")];
464     [o_mi_play setTitle: _NS("Play")];
465     [o_mi_delete setTitle: _NS("Delete")];
466     [o_mi_recursive_expand setTitle: _NS("Expand Node")];
467     [o_mi_selectall setTitle: _NS("Select All")];
468     [o_mi_info setTitle: _NS("Media Information...")];
469     [o_mi_dl_cover_art setTitle: _NS("Download Cover Art")];
470     [o_mi_preparse setTitle: _NS("Fetch Meta Data")];
471     [o_mi_revealInFinder setTitle: _NS("Reveal in Finder")];
472     [o_mm_mi_revealInFinder setTitle: _NS("Reveal in Finder")];
473     [[o_mm_mi_revealInFinder menu] setAutoenablesItems: NO];
474     [o_mi_sort_name setTitle: _NS("Sort Node by Name")];
475     [o_mi_sort_author setTitle: _NS("Sort Node by Author")];
476     [o_mi_services setTitle: _NS("Services discovery")];
477     [o_mm_mi_services setTitle: _NS("Services discovery")];
478     [o_status_field setStringValue: _NS("No items in the playlist")];
479     [o_status_field_embed setStringValue: _NS("No items in the playlist")];
481     [o_search_field setToolTip: _NS("Search in Playlist")];
482     [o_search_field_other setToolTip: _NS("Search in Playlist")];
483     [o_mi_addNode setTitle: _NS("Add Folder to Playlist")];
485     [o_save_accessory_text setStringValue: _NS("File Format:")];
486     [[o_save_accessory_popup itemAtIndex:0] setTitle: _NS("Extended M3U")];
487     [[o_save_accessory_popup itemAtIndex:1] setTitle: _NS("XML Shareable Playlist Format (XSPF)")];
488     [[o_save_accessory_popup itemAtIndex:2] setTitle: _NS("HTML Playlist")];
491 - (void)swapPlaylists:(id)newList
493         if(newList != o_outline_view)
494         {
495                 id o_search_field_temp = o_search_field;
496                 o_search_field = o_search_field_other;
497                 o_search_field_other = o_search_field_temp;
498                 [super swapPlaylists:newList];
499                 [self playlistUpdated];
500         }
503 - (void)playlistUpdated
505     /* Clear indications of any existing column sorting */
506     for( unsigned int i = 0 ; i < [[o_outline_view tableColumns] count] ; i++ )
507     {
508         [o_outline_view setIndicatorImage:nil inTableColumn:
509                             [[o_outline_view tableColumns] objectAtIndex:i]];
510     }
512     [o_outline_view setHighlightedTableColumn:nil];
513     o_tc_sortColumn = nil;
514     // TODO Find a way to keep the dict size to a minimum
515     //[o_outline_dict removeAllObjects];
516     [o_outline_view reloadData];
517     [o_sidebar updateSidebar:[self playingItem]];
518     [[[[VLCMain sharedInstance] wizard] playlistWizard] reloadOutlineView];
519     [[[[VLCMain sharedInstance] bookmarks] dataTable] reloadData];
521     playlist_t *p_playlist = pl_Get( VLCIntf );
523     PL_LOCK;
524     if( playlist_CurrentSize( p_playlist ) >= 2 )
525     {
526         [o_status_field setStringValue: [NSString stringWithFormat:
527                     _NS("%i items"),
528                 playlist_CurrentSize( p_playlist )]];
529         [o_status_field_embed setStringValue: [NSString stringWithFormat:
530                                                _NS("%i items"),
531                                                playlist_CurrentSize( p_playlist )]];        
532     }
533     else
534     {
535         if( playlist_IsEmpty( p_playlist ) )
536         {
537             [o_status_field setStringValue: _NS("No items in the playlist")];
538             [o_status_field_embed setStringValue: _NS("No items in the playlist")];
539         }
540         else
541         {
542             [o_status_field setStringValue: _NS("1 item")];
543             [o_status_field_embed setStringValue: _NS("1 item")];
544         }
545     }
546     PL_UNLOCK;
548     [self outlineViewSelectionDidChange: nil];
551 - (void)playModeUpdated
553     playlist_t *p_playlist = pl_Get( VLCIntf );
555     bool loop = var_GetBool( p_playlist, "loop" );
556     bool repeat = var_GetBool( p_playlist, "repeat" );
557     if( repeat )
558         [[[VLCMain sharedInstance] controls] repeatOne];
559     else if( loop )
560         [[[VLCMain sharedInstance] controls] repeatAll];
561     else
562         [[[VLCMain sharedInstance] controls] repeatOff];
564     [[[VLCMain sharedInstance] controls] shuffle];
567 - (void)outlineViewSelectionDidChange:(NSNotification *)notification
569     // FIXME: unsafe
570     playlist_item_t * p_item = [[o_outline_view itemAtRow:[o_outline_view selectedRow]] pointerValue];
572     if( p_item )
573     {
574         /* update the state of our Reveal-in-Finder menu items */
575         NSMutableString *o_mrl;
576         char *psz_uri = input_item_GetURI( p_item->p_input );
578         [o_mi_revealInFinder setEnabled: NO];
579         [o_mm_mi_revealInFinder setEnabled: NO];
580         if( psz_uri )
581         {
582             o_mrl = [NSMutableString stringWithUTF8String: psz_uri];
584             /* perform some checks whether it is a file and if it is local at all... */
585             NSRange prefix_range = [o_mrl rangeOfString: @"file:"];
586             if( prefix_range.location != NSNotFound )
587                 [o_mrl deleteCharactersInRange: prefix_range];
589             if( [o_mrl characterAtIndex:0] == '/' )
590             {
591                 [o_mi_revealInFinder setEnabled: YES];
592                 [o_mm_mi_revealInFinder setEnabled: YES];
593             }
594             free( psz_uri );
595         }
597         if( [[VLCMain sharedInstance] isPlaylistCollapsed] == NO )
598         {
599             /* update our info-panel to reflect the new item, if we aren't collapsed */
600             [[[VLCMain sharedInstance] info] updatePanelWithItem:p_item->p_input];
601         }
602     }
605 - (BOOL)isSelectionEmpty
607     return [o_outline_view selectedRow] == -1;
610 - (void)updateRowSelection
612     int i_row;
613     unsigned int j;
615     // FIXME: unsafe
616     playlist_t *p_playlist = pl_Get( VLCIntf );
617     playlist_item_t *p_item, *p_temp_item;
618     NSMutableArray *o_array = [NSMutableArray array];
620     PL_LOCK;
621     p_item = playlist_CurrentPlayingItem( p_playlist );
622     if( p_item == NULL )
623     {
624         PL_UNLOCK;
625         return;
626     }
628     p_temp_item = p_item;
629     while( p_temp_item->p_parent )
630     {
631         [o_array insertObject: [NSValue valueWithPointer: p_temp_item] atIndex: 0];
632         p_temp_item = p_temp_item->p_parent;
633     }
634     PL_UNLOCK;
636     for( j = 0; j < [o_array count] - 1; j++ )
637     {
638         id o_item;
639         if( ( o_item = [o_outline_dict objectForKey:
640                             [NSString stringWithFormat: @"%p",
641                             [[o_array objectAtIndex:j] pointerValue]]] ) != nil )
642         {
643             [o_outline_view expandItem: o_item];
644         }
645     }
648 /* Check if p_item is a child of p_node recursively. We need to check the item
649    existence first since OSX sometimes tries to redraw items that have been
650    deleted. We don't do it when not required since this verification takes
651    quite a long time on big playlists (yes, pretty hacky). */
653 - (BOOL)isItem: (playlist_item_t *)p_item
654                     inNode: (playlist_item_t *)p_node
655                     checkItemExistence:(BOOL)b_check
656                     locked:(BOOL)b_locked
659     playlist_t * p_playlist = pl_Get( VLCIntf );
660     playlist_item_t *p_temp_item = p_item;
662     if( p_node == p_item )
663         return YES;
665     if( p_node->i_children < 1)
666         return NO;
668     if ( p_temp_item )
669     {
670         int i;
671         if(!b_locked) PL_LOCK;
673         if( b_check )
674         {
675         /* Since outlineView: willDisplayCell:... may call this function with
676            p_items that don't exist anymore, first check if the item is still
677            in the playlist. Any cleaner solution welcomed. */
678             for( i = 0; i < p_playlist->all_items.i_size; i++ )
679             {
680                 if( ARRAY_VAL( p_playlist->all_items, i) == p_item ) break;
681                 else if ( i == p_playlist->all_items.i_size - 1 )
682                 {
683                     if(!b_locked) PL_UNLOCK;
684                     return NO;
685                 }
686             }
687         }
689         while( p_temp_item )
690         {
691             p_temp_item = p_temp_item->p_parent;
692             if( p_temp_item == p_node )
693             {
694                 if(!b_locked) PL_UNLOCK;
695                 return YES;
696             }
697         }
698         if(!b_locked) PL_UNLOCK;
699     }
700     return NO;
703 - (BOOL)isItem: (playlist_item_t *)p_item
704                     inNode: (playlist_item_t *)p_node
705                     checkItemExistence:(BOOL)b_check
707     [self isItem:p_item inNode:p_node checkItemExistence:b_check locked:NO];
710 /* This method is usefull for instance to remove the selected children of an
711    already selected node */
712 - (void)removeItemsFrom:(id)o_items ifChildrenOf:(id)o_nodes
714     unsigned int i, j;
715     for( i = 0 ; i < [o_items count] ; i++ )
716     {
717         for ( j = 0 ; j < [o_nodes count] ; j++ )
718         {
719             if( o_items == o_nodes)
720             {
721                 if( j == i ) continue;
722             }
723             if( [self isItem: [[o_items objectAtIndex:i] pointerValue]
724                     inNode: [[o_nodes objectAtIndex:j] pointerValue]
725                     checkItemExistence: NO locked:NO] )
726             {
727                 [o_items removeObjectAtIndex:i];
728                 /* We need to execute the next iteration with the same index
729                    since the current item has been deleted */
730                 i--;
731                 break;
732             }
733         }
734     }
737 - (IBAction)savePlaylist:(id)sender
739     playlist_t * p_playlist = pl_Get( VLCIntf );
741     NSSavePanel *o_save_panel = [NSSavePanel savePanel];
742     NSString * o_name = [NSString stringWithFormat: @"%@", _NS("Untitled")];
744     [o_save_panel setTitle: _NS("Save Playlist")];
745     [o_save_panel setPrompt: _NS("Save")];
746     [o_save_panel setAccessoryView: o_save_accessory_view];
748     if( [o_save_panel runModalForDirectory: nil
749             file: o_name] == NSOKButton )
750     {
751         NSString *o_filename = [o_save_panel filename];
753         if( [o_save_accessory_popup indexOfSelectedItem] == 0 )
754         {
755             NSString * o_real_filename;
756             NSRange range;
757             range.location = [o_filename length] - [@".m3u" length];
758             range.length = [@".m3u" length];
760             if( [o_filename compare:@".m3u" options: NSCaseInsensitiveSearch
761                                              range: range] != NSOrderedSame )
762             {
763                 o_real_filename = [NSString stringWithFormat: @"%@.m3u", o_filename];
764             }
765             else
766             {
767                 o_real_filename = o_filename;
768             }
769             playlist_Export( p_playlist,
770                 [o_real_filename fileSystemRepresentation],
771                 p_playlist->p_local_category, "export-m3u" );
772         }
773         else if( [o_save_accessory_popup indexOfSelectedItem] == 1 )
774         {
775             NSString * o_real_filename;
776             NSRange range;
777             range.location = [o_filename length] - [@".xspf" length];
778             range.length = [@".xspf" length];
780             if( [o_filename compare:@".xspf" options: NSCaseInsensitiveSearch
781                                              range: range] != NSOrderedSame )
782             {
783                 o_real_filename = [NSString stringWithFormat: @"%@.xspf", o_filename];
784             }
785             else
786             {
787                 o_real_filename = o_filename;
788             }
789             playlist_Export( p_playlist,
790                 [o_real_filename fileSystemRepresentation],
791                 p_playlist->p_local_category, "export-xspf" );
792         }
793         else
794         {
795             NSString * o_real_filename;
796             NSRange range;
797             range.location = [o_filename length] - [@".html" length];
798             range.length = [@".html" length];
800             if( [o_filename compare:@".html" options: NSCaseInsensitiveSearch
801                                              range: range] != NSOrderedSame )
802             {
803                 o_real_filename = [NSString stringWithFormat: @"%@.html", o_filename];
804             }
805             else
806             {
807                 o_real_filename = o_filename;
808             }
809             playlist_Export( p_playlist,
810                 [o_real_filename fileSystemRepresentation],
811                 p_playlist->p_local_category, "export-html" );
812         }
813     }
816 /* When called retrieves the selected outlineview row and plays that node or item */
817 - (IBAction)playItem:(id)sender
819     intf_thread_t * p_intf = VLCIntf;
820     playlist_t * p_playlist = pl_Get( p_intf );
822     playlist_item_t *p_item;
823     playlist_item_t *p_node = NULL;
825     p_item = [[o_outline_view itemAtRow:[o_outline_view selectedRow]] pointerValue];
827     PL_LOCK;
828     if( p_item )
829     {
830         if( p_item->i_children == -1 )
831         {
832             p_node = p_item->p_parent;
833         }
834         else
835         {
836             p_node = p_item;
837             if( p_node->i_children > 0 && p_node->pp_children[0]->i_children == -1 )
838             {
839                 p_item = p_node->pp_children[0];
840             }
841             else
842             {
843                 p_item = NULL;
844             }
845         }
846         playlist_Control( p_playlist, PLAYLIST_VIEWPLAY, pl_Locked, p_node, p_item );
847     }
848     PL_UNLOCK;
851 - (void)playSidebarItem:(id)item
853     intf_thread_t * p_intf = VLCIntf;
854     playlist_t * p_playlist = pl_Get( p_intf );
855     
856     playlist_item_t *p_item;
857     playlist_item_t *p_node = NULL;
858     
859     p_item = [item pointerValue];
860     
861     if( p_item )
862     {
863         if( p_item->i_children == -1 )
864         {
865             p_node = p_item->p_parent;
866             
867         }
868         else
869         {
870             p_node = p_item;
871             if( p_node->i_children > 0 && p_node->pp_children[0]->i_children == -1 )
872             {
873                 p_item = p_node->pp_children[0];
874             }
875             else
876             {
877                 p_item = NULL;
878             }
879         }
880         playlist_Control( p_playlist, PLAYLIST_VIEWPLAY, pl_Unlocked, p_node, p_item );
881     }
884 - (IBAction)revealItemInFinder:(id)sender
886     playlist_item_t * p_item = [[o_outline_view itemAtRow:[o_outline_view selectedRow]] pointerValue];
887     NSMutableString * o_mrl = nil;
889     if(! p_item || !p_item->p_input )
890         return;
891     
892     char *psz_uri = input_item_GetURI( p_item->p_input );
893     if( psz_uri )
894         o_mrl = [NSMutableString stringWithUTF8String: psz_uri];
896     /* perform some checks whether it is a file and if it is local at all... */
897     NSRange prefix_range = [o_mrl rangeOfString: @"file:"];
898     if( prefix_range.location != NSNotFound )
899         [o_mrl deleteCharactersInRange: prefix_range];
900     
901     if( [o_mrl characterAtIndex:0] == '/' )
902         [[NSWorkspace sharedWorkspace] selectFile: o_mrl inFileViewerRootedAtPath: o_mrl];
903 }    
905 /* When called retrieves the selected outlineview row and plays that node or item */
906 - (IBAction)preparseItem:(id)sender
908     int i_count;
909     NSMutableArray *o_to_preparse;
910     intf_thread_t * p_intf = VLCIntf;
911     playlist_t * p_playlist = pl_Get( p_intf );
913     o_to_preparse = [NSMutableArray arrayWithArray:[[o_outline_view selectedRowEnumerator] allObjects]];
914     i_count = [o_to_preparse count];
916     int i, i_row;
917     NSNumber *o_number;
918     playlist_item_t *p_item = NULL;
920     for( i = 0; i < i_count; i++ )
921     {
922         o_number = [o_to_preparse lastObject];
923         i_row = [o_number intValue];
924         p_item = [[o_outline_view itemAtRow:i_row] pointerValue];
925         [o_to_preparse removeObject: o_number];
926         [o_outline_view deselectRow: i_row];
928         if( p_item )
929         {
930             if( p_item->i_children == -1 )
931             {
932                 playlist_PreparseEnqueue( p_playlist, p_item->p_input );
933             }
934             else
935             {
936                 msg_Dbg( p_intf, "preparsing nodes not implemented" );
937             }
938         }
939     }
940     [self playlistUpdated];
943 - (IBAction)downloadCoverArt:(id)sender
945     int i_count;
946     NSMutableArray *o_to_preparse;
947     intf_thread_t * p_intf = VLCIntf;
948     playlist_t * p_playlist = pl_Get( p_intf );
950     o_to_preparse = [NSMutableArray arrayWithArray:[[o_outline_view selectedRowEnumerator] allObjects]];
951     i_count = [o_to_preparse count];
953     int i, i_row;
954     NSNumber *o_number;
955     playlist_item_t *p_item = NULL;
957     for( i = 0; i < i_count; i++ )
958     {
959         o_number = [o_to_preparse lastObject];
960         i_row = [o_number intValue];
961         p_item = [[o_outline_view itemAtRow:i_row] pointerValue];
962         [o_to_preparse removeObject: o_number];
963         [o_outline_view deselectRow: i_row];
965         if( p_item && p_item->i_children == -1 )
966         {
967             playlist_AskForArtEnqueue( p_playlist, p_item->p_input );
968         }
969     }
970     [self playlistUpdated];
973 - (IBAction)servicesChange:(id)sender
975     NSMenuItem *o_mi = (NSMenuItem *)sender;
976     NSString *o_string = [o_mi representedObject];
977     playlist_t * p_playlist = pl_Get( VLCIntf );
978     if( !playlist_IsServicesDiscoveryLoaded( p_playlist, [o_string UTF8String] ) )
979         playlist_ServicesDiscoveryAdd( p_playlist, [o_string UTF8String] );
980     else
981         playlist_ServicesDiscoveryRemove( p_playlist, [o_string UTF8String] );
983     [o_mi setState: playlist_IsServicesDiscoveryLoaded( p_playlist,
984                                           [o_string UTF8String] ) ? YES : NO];
986     [self playlistUpdated];
987     return;
990 - (IBAction)selectAll:(id)sender
992     [o_outline_view selectAll: nil];
995 - (IBAction)deleteItem:(id)sender
997     int i_count, i_row;
998     NSMutableArray *o_to_delete;
999     NSNumber *o_number;
1001     playlist_t * p_playlist;
1002     intf_thread_t * p_intf = VLCIntf;
1004     o_to_delete = [NSMutableArray arrayWithArray:[[o_outline_view selectedRowEnumerator] allObjects]];
1005     i_count = [o_to_delete count];
1007     p_playlist = pl_Get( p_intf );
1009     for( int i = 0; i < i_count; i++ )
1010     {
1011         o_number = [o_to_delete lastObject];
1012         i_row = [o_number intValue];
1013         id o_item = [o_outline_view itemAtRow: i_row];
1014         [o_outline_view deselectRow: i_row];
1016         PL_LOCK;
1017         playlist_item_t *p_item = [o_item pointerValue];
1018 #ifndef NDEBUG
1019         msg_Dbg( p_intf, "deleting item %i (of %i) with id \"%i\", pointerValue \"%p\" and %i children", i+1, i_count, 
1020                 p_item->p_input->i_id, [o_item pointerValue], p_item->i_children +1 );
1021 #endif
1022         [o_to_delete removeObject: o_number];
1024         if( p_item->i_children != -1 )
1025         //is a node and not an item
1026         {
1027             if( playlist_Status( p_playlist ) != PLAYLIST_STOPPED &&
1028                 [self isItem: playlist_CurrentPlayingItem( p_playlist ) inNode:
1029                         ((playlist_item_t *)[o_item pointerValue])
1030                         checkItemExistence: NO locked:YES] == YES )
1031                 // if current item is in selected node and is playing then stop playlist
1032                 playlist_Control(p_playlist, PLAYLIST_STOP, pl_Locked );
1033     
1034             playlist_NodeDelete( p_playlist, p_item, true, false );
1035         }
1036         else
1037             playlist_DeleteFromInput( p_playlist, p_item->p_input, pl_Locked );
1039         PL_UNLOCK;
1040         [o_outline_dict removeObjectForKey:[NSString stringWithFormat:@"%p",
1041                                                      [o_item pointerValue]]];
1042         [o_item release];
1043     }
1045     [self playlistUpdated];
1048 - (IBAction)sortNodeByName:(id)sender
1050     [self sortNode: SORT_TITLE];
1053 - (IBAction)sortNodeByAuthor:(id)sender
1055     [self sortNode: SORT_ARTIST];
1058 - (void)sortNode:(int)i_mode
1060     playlist_t * p_playlist = pl_Get( VLCIntf );
1061     playlist_item_t * p_item;
1063     if( [o_outline_view selectedRow] > -1 )
1064     {
1065         p_item = [[o_outline_view itemAtRow: [o_outline_view selectedRow]] pointerValue];
1066     }
1067     else
1068     /*If no item is selected, sort the whole playlist*/
1069     {
1070         p_item = p_playlist->p_root_category;
1071     }
1073     PL_LOCK;
1074     if( p_item->i_children > -1 ) // the item is a node
1075     {
1076         playlist_RecursiveNodeSort( p_playlist, p_item, i_mode, ORDER_NORMAL );
1077     }
1078     else
1079     {
1080         playlist_RecursiveNodeSort( p_playlist,
1081                 p_item->p_parent, i_mode, ORDER_NORMAL );
1082     }
1083     PL_UNLOCK;
1084     [self playlistUpdated];
1087 - (input_item_t *)createItem:(NSDictionary *)o_one_item
1089     intf_thread_t * p_intf = VLCIntf;
1090     playlist_t * p_playlist = pl_Get( p_intf );
1092     input_item_t *p_input;
1093     int i;
1094     BOOL b_rem = FALSE, b_dir = FALSE;
1095     NSString *o_uri, *o_name;
1096     NSArray *o_options;
1097     NSURL *o_true_file;
1099     /* Get the item */
1100     o_uri = (NSString *)[o_one_item objectForKey: @"ITEM_URL"];
1101     o_name = (NSString *)[o_one_item objectForKey: @"ITEM_NAME"];
1102     o_options = (NSArray *)[o_one_item objectForKey: @"ITEM_OPTIONS"];
1104     /* Find the name for a disc entry (i know, can you believe the trouble?) */
1105     if( ( !o_name || [o_name isEqualToString:@""] ) && [o_uri rangeOfString: @"/dev/"].location != NSNotFound )
1106     {
1107         int i_count, i_index;
1108         struct statfs *mounts = NULL;
1110         i_count = getmntinfo (&mounts, MNT_NOWAIT);
1111         /* getmntinfo returns a pointer to static data. Do not free. */
1112         for( i_index = 0 ; i_index < i_count; i_index++ )
1113         {
1114             NSMutableString *o_temp, *o_temp2;
1115             o_temp = [NSMutableString stringWithString: o_uri];
1116             o_temp2 = [NSMutableString stringWithUTF8String: mounts[i_index].f_mntfromname];
1117             [o_temp replaceOccurrencesOfString: @"/dev/rdisk" withString: @"/dev/disk" options:NSLiteralSearch range:NSMakeRange(0, [o_temp length]) ];
1118             [o_temp2 replaceOccurrencesOfString: @"s0" withString: @"" options:NSLiteralSearch range:NSMakeRange(0, [o_temp2 length]) ];
1119             [o_temp2 replaceOccurrencesOfString: @"s1" withString: @"" options:NSLiteralSearch range:NSMakeRange(0, [o_temp2 length]) ];
1121             if( strstr( [o_temp fileSystemRepresentation], [o_temp2 fileSystemRepresentation] ) != NULL )
1122             {
1123                 o_name = [[NSFileManager defaultManager] displayNameAtPath: [NSString stringWithUTF8String:mounts[i_index].f_mntonname]];
1124             }
1125         }
1126     }
1128     if( [[NSFileManager defaultManager] fileExistsAtPath:o_uri isDirectory:&b_dir] && b_dir &&
1129         [[NSWorkspace sharedWorkspace] getFileSystemInfoForPath: o_uri isRemovable: &b_rem
1130                 isWritable:NULL isUnmountable:NULL description:NULL type:NULL] && b_rem   )
1131     {
1132         /* All of this is to make sure CD's play when you D&D them on VLC */
1133         /* Converts mountpoint to a /dev file */
1134         struct statfs *buf;
1135         char *psz_dev;
1136         NSMutableString *o_temp;
1138         buf = (struct statfs *) malloc (sizeof(struct statfs));
1139         statfs( [o_uri fileSystemRepresentation], buf );
1140         psz_dev = strdup(buf->f_mntfromname);
1141         o_temp = [NSMutableString stringWithUTF8String: psz_dev ];
1142         [o_temp replaceOccurrencesOfString: @"/dev/disk" withString: @"/dev/rdisk" options:NSLiteralSearch range:NSMakeRange(0, [o_temp length]) ];
1143         [o_temp replaceOccurrencesOfString: @"s0" withString: @"" options:NSLiteralSearch range:NSMakeRange(0, [o_temp length]) ];
1144         [o_temp replaceOccurrencesOfString: @"s1" withString: @"" options:NSLiteralSearch range:NSMakeRange(0, [o_temp length]) ];
1145         o_uri = o_temp;
1146     }
1148     p_input = input_item_New( p_playlist, [o_uri fileSystemRepresentation], o_name ? [o_name UTF8String] : NULL );
1149     if( !p_input )
1150         return NULL;
1152     if( o_options )
1153     {
1154         for( i = 0; i < (int)[o_options count]; i++ )
1155         {
1156             input_item_AddOption( p_input, [[o_options objectAtIndex:i] UTF8String],
1157                                   VLC_INPUT_OPTION_TRUSTED );
1158         }
1159     }
1161     /* Recent documents menu */
1162     o_true_file = [NSURL fileURLWithPath: o_uri];
1163     if( o_true_file != nil && (BOOL)config_GetInt( p_playlist, "macosx-recentitems" ) == YES )
1164     {
1165         [[NSDocumentController sharedDocumentController]
1166             noteNewRecentDocumentURL: o_true_file];
1167     }
1168     return p_input;
1171 - (void)appendArray:(NSArray*)o_array atPos:(int)i_position enqueue:(BOOL)b_enqueue
1173     int i_item;
1174     playlist_t * p_playlist = pl_Get( VLCIntf );
1176     PL_LOCK;
1177     for( i_item = 0; i_item < (int)[o_array count]; i_item++ )
1178     {
1179         input_item_t *p_input;
1180         NSDictionary *o_one_item;
1182         /* Get the item */
1183         o_one_item = [o_array objectAtIndex: i_item];
1184         p_input = [self createItem: o_one_item];
1185         if( !p_input )
1186         {
1187             continue;
1188         }
1190         /* Add the item */
1191         /* FIXME: playlist_AddInput() can fail */
1192         
1193         playlist_AddInput( p_playlist, p_input, PLAYLIST_INSERT,
1194              i_position == -1 ? PLAYLIST_END : i_position + i_item, true,
1195          pl_Locked );
1197         if( i_item == 0 && !b_enqueue )
1198         {
1199             playlist_item_t *p_item = NULL;
1200             playlist_item_t *p_node = NULL;
1201             p_item = playlist_ItemGetByInput( p_playlist, p_input );
1202             if( p_item )
1203             {
1204                 if( p_item->i_children == -1 )
1205                     p_node = p_item->p_parent;
1206                 else
1207                 {
1208                     p_node = p_item;
1209                     if( p_node->i_children > 0 && p_node->pp_children[0]->i_children == -1 )
1210                         p_item = p_node->pp_children[0];
1211                     else
1212                         p_item = NULL;
1213                 }
1214                 playlist_Control( p_playlist, PLAYLIST_VIEWPLAY, pl_Locked, p_node, p_item );
1215             }
1216         }
1217         vlc_gc_decref( p_input );
1218     }
1219     PL_UNLOCK;
1221     [self playlistUpdated];
1224 - (void)appendNodeArray:(NSArray*)o_array inNode:(playlist_item_t *)p_node atPos:(int)i_position enqueue:(BOOL)b_enqueue
1226     int i_item;
1227     playlist_t * p_playlist = pl_Get( VLCIntf );
1229     for( i_item = 0; i_item < (int)[o_array count]; i_item++ )
1230     {
1231         input_item_t *p_input;
1232         NSDictionary *o_one_item;
1234         /* Get the item */
1235         o_one_item = [o_array objectAtIndex: i_item];
1236         p_input = [self createItem: o_one_item];
1238         if( !p_input ) continue;
1240         /* Add the item */
1241         PL_LOCK;
1242         playlist_NodeAddInput( p_playlist, p_input, p_node,
1243                                       PLAYLIST_INSERT,
1244                                       i_position == -1 ?
1245                                       PLAYLIST_END : i_position + i_item,
1246                                       pl_Locked );
1249         if( i_item == 0 && !b_enqueue )
1250         {
1251             playlist_item_t *p_item;
1252             p_item = playlist_ItemGetByInput( p_playlist, p_input );
1253             playlist_Control( p_playlist, PLAYLIST_VIEWPLAY, pl_Locked, p_node, p_item );
1254         }
1255         PL_UNLOCK;
1256         vlc_gc_decref( p_input );
1257     }
1258     [self playlistUpdated];
1261 - (NSMutableArray *)subSearchItem:(playlist_item_t *)p_item
1263     playlist_t *p_playlist = pl_Get( VLCIntf );
1264     playlist_item_t *p_selected_item;
1265     int i_current, i_selected_row;
1267     i_selected_row = [o_outline_view selectedRow];
1268     if (i_selected_row < 0)
1269         i_selected_row = 0;
1271     p_selected_item = (playlist_item_t *)[[o_outline_view itemAtRow:
1272                                             i_selected_row] pointerValue];
1274     for( i_current = 0; i_current < p_item->i_children ; i_current++ )
1275     {
1276         char *psz_temp;
1277         NSString *o_current_name, *o_current_author;
1279         PL_LOCK;
1280         o_current_name = [NSString stringWithUTF8String:
1281             p_item->pp_children[i_current]->p_input->psz_name];
1282         psz_temp = input_item_GetInfo( p_item->p_input ,
1283                    _("Meta-information"),_("Artist") );
1284         o_current_author = [NSString stringWithUTF8String: psz_temp];
1285         free( psz_temp);
1286         PL_UNLOCK;
1288         if( p_selected_item == p_item->pp_children[i_current] &&
1289                     b_selected_item_met == NO )
1290         {
1291             b_selected_item_met = YES;
1292         }
1293         else if( p_selected_item == p_item->pp_children[i_current] &&
1294                     b_selected_item_met == YES )
1295         {
1296             return NULL;
1297         }
1298         else if( b_selected_item_met == YES &&
1299                     ( [o_current_name rangeOfString:[o_search_field
1300                         stringValue] options:NSCaseInsensitiveSearch].length ||
1301                       [o_current_author rangeOfString:[o_search_field
1302                         stringValue] options:NSCaseInsensitiveSearch].length ) )
1303         {
1304             /*Adds the parent items in the result array as well, so that we can
1305             expand the tree*/
1306             return [NSMutableArray arrayWithObject: [NSValue
1307                             valueWithPointer: p_item->pp_children[i_current]]];
1308         }
1309         if( p_item->pp_children[i_current]->i_children > 0 )
1310         {
1311             id o_result = [self subSearchItem:
1312                                             p_item->pp_children[i_current]];
1313             if( o_result != NULL )
1314             {
1315                 [o_result insertObject: [NSValue valueWithPointer:
1316                                 p_item->pp_children[i_current]] atIndex:0];
1317                 return o_result;
1318             }
1319         }
1320     }
1321     return NULL;
1324 - (IBAction)searchItem:(id)sender
1326     playlist_t * p_playlist = pl_Get( VLCIntf );
1327     id o_result;
1329     unsigned int i;
1330     int i_row = -1;
1332     b_selected_item_met = NO;
1334         /*First, only search after the selected item:*
1335          *(b_selected_item_met = NO)                 */
1336     o_result = [self subSearchItem:p_playlist->p_root_category];
1337     if( o_result == NULL )
1338     {
1339         /* If the first search failed, search again from the beginning */
1340         o_result = [self subSearchItem:p_playlist->p_root_category];
1341     }
1342     if( o_result != NULL )
1343     {
1344         int i_start;
1345         if( [[o_result objectAtIndex: 0] pointerValue] ==
1346                                                     p_playlist->p_local_category )
1347         i_start = 1;
1348         else
1349         i_start = 0;
1351         for( i = i_start ; i < [o_result count] - 1 ; i++ )
1352         {
1353             [o_outline_view expandItem: [o_outline_dict objectForKey:
1354                         [NSString stringWithFormat: @"%p",
1355                         [[o_result objectAtIndex: i] pointerValue]]]];
1356         }
1357         i_row = [o_outline_view rowForItem: [o_outline_dict objectForKey:
1358                         [NSString stringWithFormat: @"%p",
1359                         [[o_result objectAtIndex: [o_result count] - 1 ]
1360                         pointerValue]]]];
1361     }
1362     if( i_row > -1 )
1363     {
1364                 [o_outline_view selectRowIndexes:[NSIndexSet indexSetWithIndex:i_row] byExtendingSelection:NO];
1365         [o_outline_view scrollRowToVisible: i_row];
1366     }
1369 - (IBAction)recursiveExpandNode:(id)sender
1371     id o_item = [o_outline_view itemAtRow: [o_outline_view selectedRow]];
1372     playlist_item_t *p_item = (playlist_item_t *)[o_item pointerValue];
1374     if( ![[o_outline_view dataSource] outlineView: o_outline_view
1375                                                     isItemExpandable: o_item] )
1376     {
1377         o_item = [o_outline_dict objectForKey: [NSString
1378                    stringWithFormat: @"%p", p_item->p_parent]];
1379     }
1381     /* We need to collapse the node first, since OSX refuses to recursively
1382        expand an already expanded node, even if children nodes are collapsed. */
1383     [o_outline_view collapseItem: o_item collapseChildren: YES];
1384     [o_outline_view expandItem: o_item expandChildren: YES];
1387 - (NSMenu *)menuForEvent:(NSEvent *)o_event
1389     NSPoint pt;
1390     bool b_rows;
1391     bool b_item_sel;
1393     pt = [o_outline_view convertPoint: [o_event locationInWindow]
1394                                                  fromView: nil];
1395     int row = [o_outline_view rowAtPoint:pt];
1396     if( row != -1 )
1397         [o_outline_view selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO];
1399     b_item_sel = ( row != -1 && [o_outline_view selectedRow] != -1 );
1400     b_rows = [o_outline_view numberOfRows] != 0;
1402     [o_mi_play setEnabled: b_item_sel];
1403     [o_mi_delete setEnabled: b_item_sel];
1404     [o_mi_selectall setEnabled: b_rows];
1405     [o_mi_info setEnabled: b_item_sel];
1406     [o_mi_preparse setEnabled: b_item_sel];
1407     [o_mi_recursive_expand setEnabled: b_item_sel];
1408     [o_mi_sort_name setEnabled: b_item_sel];
1409     [o_mi_sort_author setEnabled: b_item_sel];
1411     return( o_ctx_menu );
1414 - (void)outlineView: (NSOutlineView *)o_tv
1415                   didClickTableColumn:(NSTableColumn *)o_tc
1417     int i_mode, i_type = 0;
1418     intf_thread_t *p_intf = VLCIntf;
1420     playlist_t *p_playlist = pl_Get( p_intf );
1422     /* Check whether the selected table column header corresponds to a
1423        sortable table column*/
1424     if( !( o_tc == o_tc_name || o_tc == o_tc_author ) )
1425     {
1426         return;
1427     }
1429     if( o_tc_sortColumn == o_tc )
1430     {
1431         b_isSortDescending = !b_isSortDescending;
1432     }
1433     else
1434     {
1435         b_isSortDescending = false;
1436     }
1438     if( o_tc == o_tc_name )
1439     {
1440         i_mode = SORT_TITLE;
1441     }
1442     else if( o_tc == o_tc_author )
1443     {
1444         i_mode = SORT_ARTIST;
1445     }
1447     if( b_isSortDescending )
1448     {
1449         i_type = ORDER_REVERSE;
1450     }
1451     else
1452     {
1453         i_type = ORDER_NORMAL;
1454     }
1456     PL_LOCK;
1457     playlist_RecursiveNodeSort( p_playlist, p_playlist->p_root_category, i_mode, i_type );
1458     PL_UNLOCK;
1460     [self playlistUpdated];
1462     o_tc_sortColumn = o_tc;
1463     [o_outline_view setHighlightedTableColumn:o_tc];
1465     if( b_isSortDescending )
1466     {
1467         [o_outline_view setIndicatorImage:o_descendingSortingImage
1468                                                         inTableColumn:o_tc];
1469     }
1470     else
1471     {
1472         [o_outline_view setIndicatorImage:o_ascendingSortingImage
1473                                                         inTableColumn:o_tc];
1474     }
1478 - (void)outlineView:(NSOutlineView *)outlineView
1479                                 willDisplayCell:(id)cell
1480                                 forTableColumn:(NSTableColumn *)tableColumn
1481                                 item:(id)item
1483     playlist_t *p_playlist = pl_Get( VLCIntf );
1485     id o_playing_item;
1487     PL_LOCK;
1488     o_playing_item = [o_outline_dict objectForKey:
1489                 [NSString stringWithFormat:@"%p",  playlist_CurrentPlayingItem( p_playlist )]];
1490     PL_UNLOCK;
1492     if( [self isItem: [o_playing_item pointerValue] inNode:
1493                         [item pointerValue] checkItemExistence: YES]
1494                         || [o_playing_item isEqual: item] )
1495     {
1496         [cell setFont: [[NSFontManager sharedFontManager] convertFont:[cell font] toHaveTrait:NSBoldFontMask]];
1497     }
1498     else
1499     {
1500         [cell setFont: [[NSFontManager sharedFontManager] convertFont:[cell font] toNotHaveTrait:NSBoldFontMask]];
1501     }
1504 - (id)playingItem
1506     playlist_t *p_playlist = pl_Get( VLCIntf );
1507     
1508     id o_playing_item;
1510     PL_LOCK;
1511     o_playing_item = [o_outline_dict objectForKey:
1512                       [NSString stringWithFormat:@"%p",  playlist_CurrentPlayingItem( p_playlist )]];
1513     PL_UNLOCK;
1515     return o_playing_item;
1518 - (IBAction)addNode:(id)sender
1520     playlist_t * p_playlist = pl_Get( VLCIntf );
1521     vlc_thread_set_priority( p_playlist, VLC_THREAD_PRIORITY_LOW );
1523     PL_LOCK;
1524     playlist_NodeCreate( p_playlist, _("Empty Folder"),
1525                                       p_playlist->p_local_category, 0, NULL );
1526     PL_UNLOCK;
1528     [self playlistUpdated];
1530 @end
1532 @implementation VLCPlaylist (NSOutlineViewDataSource)
1534 - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item
1536     id o_value = [super outlineView: outlineView child: index ofItem: item];
1537     playlist_t *p_playlist = pl_Get( VLCIntf );
1539     PL_LOCK;
1540     if( playlist_CurrentSize( p_playlist )  >= 2 )
1541     {
1542         [o_status_field setStringValue: [NSString stringWithFormat:
1543                     _NS("%i items"),
1544              playlist_CurrentSize( p_playlist )]];
1545         [o_status_field_embed setStringValue: [NSString stringWithFormat:
1546                                                _NS("%i items"),
1547                                                playlist_CurrentSize( p_playlist )]];
1548     }
1549     else
1550     {
1551         if( playlist_IsEmpty( p_playlist ) )
1552         {
1553             [o_status_field setStringValue: _NS("No items in the playlist")];
1554             [o_status_field_embed setStringValue: _NS("No items in the playlist")];
1555         }
1556         else
1557         {
1558             [o_status_field setStringValue: _NS("1 item")];
1559             [o_status_field_embed setStringValue: _NS("1 item")];
1560         }
1561     }
1562     PL_UNLOCK;
1564     [o_outline_dict setObject:o_value forKey:[NSString stringWithFormat:@"%p",
1565                                                     [o_value pointerValue]]];
1566     return o_value;
1570 /* Required for drag & drop and reordering */
1571 - (BOOL)outlineView:(NSOutlineView *)outlineView writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pboard
1573     unsigned int i;
1574     playlist_t *p_playlist = pl_Get( VLCIntf );
1576     /* First remove the items that were moved during the last drag & drop
1577        operation */
1578     [o_items_array removeAllObjects];
1579     [o_nodes_array removeAllObjects];
1581     for( i = 0 ; i < [items count] ; i++ )
1582     {
1583         id o_item = [items objectAtIndex: i];
1585         /* Refuse to move items that are not in the General Node
1586            (Service Discovery) */
1587         if( ![self isItem: [o_item pointerValue] inNode:
1588                         p_playlist->p_local_category checkItemExistence: NO] &&
1589             var_CreateGetBool( p_playlist, "media-library" ) &&
1590             ![self isItem: [o_item pointerValue] inNode:
1591                         p_playlist->p_ml_category checkItemExistence: NO] ||
1592             [o_item pointerValue] == p_playlist->p_local_category ||
1593             [o_item pointerValue] == p_playlist->p_ml_category )
1594         {
1595             return NO;
1596         }
1597         /* Fill the items and nodes to move in 2 different arrays */
1598         if( ((playlist_item_t *)[o_item pointerValue])->i_children > 0 )
1599             [o_nodes_array addObject: o_item];
1600         else
1601             [o_items_array addObject: o_item];
1602     }
1604     /* Now we need to check if there are selected items that are in already
1605        selected nodes. In that case, we only want to move the nodes */
1606     [self removeItemsFrom: o_nodes_array ifChildrenOf: o_nodes_array];
1607     [self removeItemsFrom: o_items_array ifChildrenOf: o_nodes_array];
1609     /* We add the "VLCPlaylistItemPboardType" type to be able to recognize
1610        a Drop operation coming from the playlist. */
1612     [pboard declareTypes: [NSArray arrayWithObjects:
1613         @"VLCPlaylistItemPboardType", nil] owner: self];
1614     [pboard setData:[NSData data] forType:@"VLCPlaylistItemPboardType"];
1616     return YES;
1619 - (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id <NSDraggingInfo>)info proposedItem:(id)item proposedChildIndex:(NSInteger)index
1621     playlist_t *p_playlist = pl_Get( VLCIntf );
1622     NSPasteboard *o_pasteboard = [info draggingPasteboard];
1624     if( !p_playlist ) return NSDragOperationNone;
1626     /* Dropping ON items is not allowed if item is not a node */
1627     if( item )
1628     {
1629         if( index == NSOutlineViewDropOnItemIndex &&
1630                 ((playlist_item_t *)[item pointerValue])->i_children == -1 )
1631         {
1632             return NSDragOperationNone;
1633         }
1634     }
1636     /* Don't allow on drop on playlist root element's child */
1637     if( !item && index != NSOutlineViewDropOnItemIndex)
1638     {
1639         return NSDragOperationNone;
1640     }
1642     /* We refuse to drop an item in anything else than a child of the General
1643        Node. We still accept items that would be root nodes of the outlineview
1644        however, to allow drop in an empty playlist. */
1645     if( !( ([self isItem: [item pointerValue] inNode: p_playlist->p_local_category checkItemExistence: NO] || 
1646         ( var_CreateGetBool( p_playlist, "media-library" ) && [self isItem: [item pointerValue] inNode: p_playlist->p_ml_category checkItemExistence: NO] ) ) || item == nil ) )
1647     {
1648         return NSDragOperationNone;
1649     }
1651     /* Drop from the Playlist */
1652     if( [[o_pasteboard types] containsObject: @"VLCPlaylistItemPboardType"] )
1653     {
1654         unsigned int i;
1655         for( i = 0 ; i < [o_nodes_array count] ; i++ )
1656         {
1657             /* We refuse to Drop in a child of an item we are moving */
1658             if( [self isItem: [item pointerValue] inNode:
1659                     [[o_nodes_array objectAtIndex: i] pointerValue]
1660                     checkItemExistence: NO] )
1661             {
1662                 return NSDragOperationNone;
1663             }
1664         }
1665         return NSDragOperationMove;
1666     }
1668     /* Drop from the Finder */
1669     else if( [[o_pasteboard types] containsObject: NSFilenamesPboardType] )
1670     {
1671         return NSDragOperationGeneric;
1672     }
1673     return NSDragOperationNone;
1676 - (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id <NSDraggingInfo>)info item:(id)item childIndex:(NSInteger)index
1678     playlist_t * p_playlist =  pl_Get( VLCIntf );
1679     NSPasteboard *o_pasteboard = [info draggingPasteboard];
1681     /* Drag & Drop inside the playlist */
1682     if( [[o_pasteboard types] containsObject: @"VLCPlaylistItemPboardType"] )
1683     {
1684         int i_row, i_removed_from_node = 0;
1685         unsigned int i;
1686         playlist_item_t *p_new_parent, *p_item = NULL;
1687         NSArray *o_all_items = [o_nodes_array arrayByAddingObjectsFromArray:
1688                                                                 o_items_array];
1689         /* If the item is to be dropped as root item of the outline, make it a
1690            child of the General node.
1691            Else, choose the proposed parent as parent. */
1692         if( item == nil ) p_new_parent = p_playlist->p_local_category;
1693         else p_new_parent = [item pointerValue];
1695         /* Make sure the proposed parent is a node.
1696            (This should never be true) */
1697         if( p_new_parent->i_children < 0 )
1698         {
1699             return NO;
1700         }
1702         for( i = 0; i < [o_all_items count]; i++ )
1703         {
1704             playlist_item_t *p_old_parent = NULL;
1705             int i_old_index = 0;
1707             p_item = [[o_all_items objectAtIndex:i] pointerValue];
1708             p_old_parent = p_item->p_parent;
1709             if( !p_old_parent )
1710             continue;
1711             /* We may need the old index later */
1712             if( p_new_parent == p_old_parent )
1713             {
1714                 int j;
1715                 for( j = 0; j < p_old_parent->i_children; j++ )
1716                 {
1717                     if( p_old_parent->pp_children[j] == p_item )
1718                     {
1719                         i_old_index = j;
1720                         break;
1721                     }
1722                 }
1723             }
1725             PL_LOCK;
1726             // Actually detach the item from the old position
1727             if( playlist_NodeRemoveItem( p_playlist, p_item, p_old_parent ) ==
1728                 VLC_SUCCESS )
1729             {
1730                 int i_new_index;
1731                 /* Calculate the new index */
1732                 if( index == -1 )
1733                 i_new_index = -1;
1734                 /* If we move the item in the same node, we need to take into
1735                    account that one item will be deleted */
1736                 else
1737                 {
1738                     if ((p_new_parent == p_old_parent &&
1739                                    i_old_index < index + (int)i) )
1740                     {
1741                         i_removed_from_node++;
1742                     }
1743                     i_new_index = index + i - i_removed_from_node;
1744                 }
1745                 // Reattach the item to the new position
1746                 playlist_NodeInsert( p_playlist, p_item, p_new_parent, i_new_index );
1747             }
1748             PL_UNLOCK;
1749         }
1750         [self playlistUpdated];
1751         i_row = [o_outline_view rowForItem:[o_outline_dict
1752             objectForKey:[NSString stringWithFormat: @"%p",
1753             [[o_all_items objectAtIndex: 0] pointerValue]]]];
1755         if( i_row == -1 )
1756         {
1757             i_row = [o_outline_view rowForItem:[o_outline_dict
1758             objectForKey:[NSString stringWithFormat: @"%p", p_new_parent]]];
1759         }
1761         [o_outline_view deselectAll: self];
1762         [o_outline_view selectRowIndexes:[NSIndexSet indexSetWithIndex:i_row] byExtendingSelection:NO];
1763         [o_outline_view scrollRowToVisible: i_row];
1765         return YES;
1766     }
1768     else if( [[o_pasteboard types] containsObject: NSFilenamesPboardType] )
1769     {
1770         int i;
1771         playlist_item_t *p_node = [item pointerValue];
1773         NSArray *o_array = [NSArray array];
1774         NSArray *o_values = [[o_pasteboard propertyListForType:
1775                                         NSFilenamesPboardType]
1776                                 sortedArrayUsingSelector:
1777                                         @selector(caseInsensitiveCompare:)];
1779         for( i = 0; i < (int)[o_values count]; i++)
1780         {
1781             NSDictionary *o_dic;
1782             o_dic = [NSDictionary dictionaryWithObject:[o_values
1783                         objectAtIndex:i] forKey:@"ITEM_URL"];
1784             o_array = [o_array arrayByAddingObject: o_dic];
1785         }
1787         if ( item == nil )
1788         {
1789             [self appendArray:o_array atPos:index enqueue: YES];
1790         }
1791         else
1792         {
1793             assert( p_node->i_children != -1 );
1794             [self appendNodeArray:o_array inNode: p_node
1795                 atPos:index enqueue:YES];
1796         }
1797         return YES;
1798     }
1799     return NO;
1801 @end