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