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