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