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