]> git.sesse.net Git - vlc/blob - modules/gui/macosx/playlist.m
Temporary disable NSSearchField button
[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         //i_moveRow = -1;
107     }
108     return self;
109 }
110
111 - (void)awakeFromNib
112 {
113     playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
114                                           FIND_ANYWHERE );
115     vlc_list_t *p_list = vlc_list_find( p_playlist, VLC_OBJECT_MODULE,
116                                         FIND_ANYWHERE );
117
118     int i_index;
119     i_current_view = VIEW_CATEGORY;
120     playlist_ViewUpdate( p_playlist, i_current_view );
121
122     [o_outline_view setTarget: self];
123     [o_outline_view setDelegate: self];
124     [o_outline_view setDataSource: self];
125
126     [o_outline_view setDoubleAction: @selector(playItem:)];
127
128     [o_outline_view registerForDraggedTypes:
129         [NSArray arrayWithObjects: NSFilenamesPboardType, nil]];
130     [o_outline_view setIntercellSpacing: NSMakeSize (0.0, 1.0)];
131
132 /* We need to check whether _defaultTableHeaderSortImage exists, since it 
133 belongs to an Apple hidden private API, and then can "disapear" at any time*/
134
135     if( [[NSOutlineView class] respondsToSelector:@selector(_defaultTableHeaderSortImage)] )
136     {
137         o_ascendingSortingImage = [[NSOutlineView class] _defaultTableHeaderSortImage];
138     }
139     else
140     {
141         o_ascendingSortingImage = nil;
142     }
143
144     if( [[NSOutlineView class] respondsToSelector:@selector(_defaultTableHeaderReverseSortImage)] )
145     {
146         o_descendingSortingImage = [[NSOutlineView class] _defaultTableHeaderReverseSortImage];
147     }
148     else
149     {
150         o_descendingSortingImage = nil;
151     }
152
153     o_tc_sortColumn = nil;
154
155     for( i_index = 0; i_index < p_list->i_count; i_index++ )
156     {
157         NSMenuItem * o_lmi;
158         module_t * p_parser = (module_t *)p_list->p_values[i_index].p_object ;
159
160         if( !strcmp( p_parser->psz_capability, "services_discovery" ) )
161         {
162             /* create the menu entries used in the playlist menu */
163             o_lmi = [[o_mi_services submenu] addItemWithTitle:
164                      [NSString stringWithUTF8String:
165                      p_parser->psz_longname ? p_parser->psz_longname :
166                      ( p_parser->psz_shortname ? p_parser->psz_shortname:
167                      p_parser->psz_object_name)]
168                                              action: @selector(servicesChange:)
169                                              keyEquivalent: @""];
170             [o_lmi setTarget: self];
171             [o_lmi setRepresentedObject:
172                    [NSString stringWithCString: p_parser->psz_object_name]];
173             if( playlist_IsServicesDiscoveryLoaded( p_playlist,
174                     p_parser->psz_object_name ) )
175                 [o_lmi setState: NSOnState];
176                 
177             /* create the menu entries for the main menu */
178             o_lmi = [[o_mm_mi_services submenu] addItemWithTitle:
179                      [NSString stringWithUTF8String:
180                      p_parser->psz_longname ? p_parser->psz_longname :
181                      ( p_parser->psz_shortname ? p_parser->psz_shortname:
182                      p_parser->psz_object_name)]
183                                              action: @selector(servicesChange:)
184                                              keyEquivalent: @""];
185             [o_lmi setTarget: self];
186             [o_lmi setRepresentedObject:
187                    [NSString stringWithCString: p_parser->psz_object_name]];
188             if( playlist_IsServicesDiscoveryLoaded( p_playlist,
189                     p_parser->psz_object_name ) )
190                 [o_lmi setState: NSOnState];
191         }
192     }
193     vlc_list_release( p_list );
194     vlc_object_release( p_playlist );
195
196     /* Change the simple textfield into a searchField if we can... */
197 #if 0
198     if( MACOS_VERSION >= 10.3 )
199     {
200         NSView *o_parentview = [o_status_field superview];
201         NSSearchField *o_better_search_field = [[NSSearchField alloc]initWithFrame:[o_search_field frame]];
202         [o_better_search_field setRecentsAutosaveName:@"VLC media player search"];
203         [o_better_search_field setDelegate:self];
204         [[NSNotificationCenter defaultCenter] addObserver: self
205             selector: @selector(searchfieldChanged:)
206             name: NSControlTextDidChangeNotification
207             object: o_better_search_field];
208
209         [o_better_search_field setTarget:self];
210         [o_better_search_field setAction:@selector(searchItem:)];
211
212         [o_better_search_field setAutoresizingMask:NSViewMinXMargin];
213         [o_parentview addSubview:o_better_search_field];
214         [o_search_field setHidden:YES];
215     }
216 #endif
217     [self initStrings];
218     //[self playlistUpdated];
219 }
220
221 - (void)searchfieldChanged:(NSNotification *)o_notification
222 {
223     [o_search_field setStringValue:[[o_notification object] stringValue]];
224 }
225
226 - (void)initStrings
227 {
228     [o_mi_save_playlist setTitle: _NS("Save Playlist...")];
229     [o_mi_play setTitle: _NS("Play")];
230     [o_mi_delete setTitle: _NS("Delete")];
231     [o_mi_selectall setTitle: _NS("Select All")];
232     [o_mi_info setTitle: _NS("Properties")];
233     [o_mi_sort_name setTitle: _NS("Sort Node by Name")];
234     [o_mi_sort_author setTitle: _NS("Sort Node by Author")];
235     [o_mi_services setTitle: _NS("Services discovery")];
236     [[o_tc_name headerCell] setStringValue:_NS("Name")];
237     [[o_tc_author headerCell] setStringValue:_NS("Author")];
238     [[o_tc_duration headerCell] setStringValue:_NS("Duration")];
239     [o_status_field setStringValue: [NSString stringWithFormat:
240                         _NS("no items in playlist")]];
241
242     [o_random_ckb setTitle: _NS("Random")];
243 #if 0
244     [o_search_button setTitle: _NS("Search")];
245 #endif
246     [[o_loop_popup itemAtIndex:0] setTitle: _NS("Standard Play")];
247     [[o_loop_popup itemAtIndex:1] setTitle: _NS("Repeat One")];
248     [[o_loop_popup itemAtIndex:2] setTitle: _NS("Repeat All")];
249 }
250
251 - (NSOutlineView *)outlineView
252 {
253     return o_outline_view;
254 }
255
256 - (void)playlistUpdated
257 {
258     unsigned int i;
259
260     /* Clear indications of any existing column sorting*/
261     for( i = 0 ; i < [[o_outline_view tableColumns] count] ; i++ )
262     {
263         [o_outline_view setIndicatorImage:nil inTableColumn:
264                             [[o_outline_view tableColumns] objectAtIndex:i]];
265     }
266
267     [o_outline_view setHighlightedTableColumn:nil];
268     o_tc_sortColumn = nil;
269     // TODO Find a way to keep the dict size to a minimum
270     //[o_outline_dict removeAllObjects];
271     [o_outline_view reloadData];
272 }
273
274 - (void)playModeUpdated
275 {
276     playlist_t *p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
277                                           FIND_ANYWHERE );
278     vlc_value_t val, val2;
279
280     if( p_playlist == NULL )
281     {
282         return;
283     }
284
285     var_Get( p_playlist, "loop", &val2 );
286     var_Get( p_playlist, "repeat", &val );
287     if( val.b_bool == VLC_TRUE )
288     {
289         [o_loop_popup selectItemAtIndex: 1];
290    }
291     else if( val2.b_bool == VLC_TRUE )
292     {
293         [o_loop_popup selectItemAtIndex: 2];
294     }
295     else
296     {
297         [o_loop_popup selectItemAtIndex: 0];
298     }
299
300     var_Get( p_playlist, "random", &val );
301     [o_random_ckb setState: val.b_bool];
302
303     vlc_object_release( p_playlist );
304 }
305
306 - (void)updateRowSelection
307 {
308     int i,i_row;
309     unsigned int j;
310
311     playlist_t *p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
312                                           FIND_ANYWHERE );
313     playlist_item_t *p_item, *p_temp_item;
314     NSMutableArray *o_array = [NSMutableArray array];
315
316     if( p_playlist == NULL )
317         return;
318
319     p_item = p_playlist->status.p_item;
320     if( p_item == NULL ) return;
321
322     p_temp_item = p_item;
323     while( p_temp_item->i_parents > 0 )
324     {
325         [o_array insertObject: [NSValue valueWithPointer: p_temp_item] atIndex: 0];
326         for (i = 0 ; i < p_temp_item->i_parents ; i++)
327         {
328             if( p_temp_item->pp_parents[i]->i_view == i_current_view )
329             {
330                 p_temp_item = p_temp_item->pp_parents[i]->p_parent;
331                 break;
332             }
333         }
334     }
335
336     for (j = 0 ; j < [o_array count] - 1 ; j++)
337     {
338         id o_item;
339         if( ( o_item = [o_outline_dict objectForKey:
340                             [NSString stringWithFormat: @"%p",
341                             [[o_array objectAtIndex:j] pointerValue]]] ) != nil )
342             [o_outline_view expandItem: o_item];
343
344     }
345
346     i_row = [o_outline_view rowForItem:[o_outline_dict
347             objectForKey:[NSString stringWithFormat: @"%p", p_item]]];
348
349     [o_outline_view selectRow: i_row byExtendingSelection: NO];
350     [o_outline_view scrollRowToVisible: i_row];
351
352     vlc_object_release(p_playlist);
353 }
354
355
356 - (BOOL)isItem: (playlist_item_t *)p_item inNode: (playlist_item_t *)p_node
357 {
358     playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
359                                           FIND_ANYWHERE );
360     playlist_item_t *p_temp_item = p_item;
361
362     if( p_playlist == NULL )
363     {
364         return NO;
365     }
366
367     if ( p_temp_item )
368     {
369         while( p_temp_item->i_parents > 0 )
370         {
371             int i;
372             for( i = 0; i < p_temp_item->i_parents ; i++ )
373             {
374                 if( p_temp_item->pp_parents[i]->i_view == i_current_view )
375                 {
376                     if( p_temp_item->pp_parents[i]->p_parent == p_node )
377                     {
378                         vlc_object_release( p_playlist );
379                         return YES;
380                     }
381                     else
382                     {
383                         p_temp_item = p_temp_item->pp_parents[i]->p_parent;
384                         break;
385                     }
386                 }
387             }
388         }
389     }
390
391     vlc_object_release( p_playlist );
392     return NO;
393 }
394
395
396 /* When called retrieves the selected outlineview row and plays that node or item */
397 - (IBAction)playItem:(id)sender
398 {
399     intf_thread_t * p_intf = VLCIntf;
400     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
401                                                        FIND_ANYWHERE );
402
403     if( p_playlist != NULL )
404     {
405         playlist_item_t *p_item;
406         playlist_item_t *p_node = NULL;
407         int i;
408
409         p_item = [[o_outline_view itemAtRow:[o_outline_view selectedRow]] pointerValue];
410
411         if( p_item )
412         {
413             if( p_item->i_children == -1 )
414             {
415                 for( i = 0 ; i < p_item->i_parents ; i++ )
416                 {
417                     if( p_item->pp_parents[i]->i_view == i_current_view )
418                     {
419                         p_node = p_item->pp_parents[i]->p_parent;
420                     }
421                 }
422             }
423             else
424             {
425                 p_node = p_item;
426                 if( p_node->i_children > 0 && p_node->pp_children[0]->i_children == -1 )
427                 {
428                     p_item = p_node->pp_children[0];
429                 }
430                 else
431                 {
432                     p_item = NULL;
433                 }
434             }
435             playlist_Control( p_playlist, PLAYLIST_VIEWPLAY, i_current_view, p_node, p_item );
436         }
437         vlc_object_release( p_playlist );
438     }
439 }
440
441 - (IBAction)servicesChange:(id)sender
442 {
443     NSMenuItem *o_mi = (NSMenuItem *)sender;
444     NSString *o_string = [o_mi representedObject];
445     playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
446                                           FIND_ANYWHERE );
447     if( !playlist_IsServicesDiscoveryLoaded( p_playlist, [o_string cString] ) )
448         playlist_ServicesDiscoveryAdd( p_playlist, [o_string cString] );
449     else
450         playlist_ServicesDiscoveryRemove( p_playlist, [o_string cString] );
451
452     [o_mi setState: playlist_IsServicesDiscoveryLoaded( p_playlist,
453                                           [o_string cString] ) ? YES : NO];
454
455     i_current_view = VIEW_CATEGORY;
456     playlist_ViewUpdate( p_playlist, i_current_view );
457     vlc_object_release( p_playlist );
458     [self playlistUpdated];
459     return;
460 }
461
462 - (IBAction)selectAll:(id)sender
463 {
464     [o_outline_view selectAll: nil];
465 }
466
467 - (IBAction)deleteItem:(id)sender
468 {
469     int i, i_count, i_row;
470     NSMutableArray *o_to_delete;
471     NSNumber *o_number;
472
473     playlist_t * p_playlist;
474     intf_thread_t * p_intf = VLCIntf;
475
476     p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
477                                           FIND_ANYWHERE );
478
479     if ( p_playlist == NULL )
480     {
481         return;
482     }
483     o_to_delete = [NSMutableArray arrayWithArray:[[o_outline_view selectedRowEnumerator] allObjects]];
484     i_count = [o_to_delete count];
485
486     for( i = 0; i < i_count; i++ )
487     {
488         playlist_item_t * p_item;
489         o_number = [o_to_delete lastObject];
490         i_row = [o_number intValue];
491
492         [o_to_delete removeObject: o_number];
493         [o_outline_view deselectRow: i_row];
494
495         p_item = (playlist_item_t *)[[o_outline_view itemAtRow: i_row] pointerValue];
496
497         if( p_item->i_children > -1 ) //is a node and not an item
498         {
499             if( p_playlist->status.i_status != PLAYLIST_STOPPED &&
500                 [self isItem: p_playlist->status.p_item inNode: p_item] == YES )
501             {
502                 // if current item is in selected node and is playing then stop playlist
503                 playlist_Stop( p_playlist );
504             }
505             playlist_NodeDelete( p_playlist, p_item, VLC_TRUE, VLC_FALSE );
506         }
507         else
508         {
509             if( p_playlist->status.i_status != PLAYLIST_STOPPED &&
510                 p_playlist->status.p_item == [[o_outline_view itemAtRow: i_row] pointerValue] )
511             {
512                 playlist_Stop( p_playlist );
513             }
514             playlist_LockDelete( p_playlist, p_item->input.i_id );
515         }
516     }
517     [self playlistUpdated];
518     vlc_object_release( p_playlist );
519 }
520
521 - (IBAction)sortNodeByName:(id)sender
522 {
523     [self sortNode: SORT_TITLE];
524 }
525
526 - (IBAction)sortNodeByAuthor:(id)sender
527 {
528     [self sortNode: SORT_AUTHOR];
529 }
530
531 - (void)sortNode:(int)i_mode
532 {
533     playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
534                                           FIND_ANYWHERE );
535     playlist_item_t * p_item;
536
537     if (p_playlist == NULL)
538     {
539         return;
540     }
541
542     if( [o_outline_view selectedRow] > -1 )
543     {
544         p_item = [[o_outline_view itemAtRow: [o_outline_view selectedRow]]
545                                                                 pointerValue];
546     }
547     else
548     /*If no item is selected, sort the whole playlist*/
549     {
550         playlist_view_t * p_view = playlist_ViewFind( p_playlist, i_current_view );
551         p_item = p_view->p_root;
552     }
553
554     if( p_item->i_children > -1 ) // the item is a node
555     {
556         vlc_mutex_lock( &p_playlist->object_lock );
557         playlist_RecursiveNodeSort( p_playlist, p_item, i_mode, ORDER_NORMAL );
558         vlc_mutex_unlock( &p_playlist->object_lock );
559     }
560     else
561     {
562         int i;
563
564         for( i = 0 ; i < p_item->i_parents ; i++ )
565         {
566             if( p_item->pp_parents[i]->i_view == i_current_view )
567             {
568                 vlc_mutex_lock( &p_playlist->object_lock );
569                 playlist_RecursiveNodeSort( p_playlist,
570                         p_item->pp_parents[i]->p_parent, i_mode, ORDER_NORMAL );
571                 vlc_mutex_unlock( &p_playlist->object_lock );
572                 break;
573             }
574         }
575     }
576     vlc_object_release( p_playlist );
577     [self playlistUpdated];
578 }
579
580 - (playlist_item_t *)createItem:(NSDictionary *)o_one_item
581 {
582     intf_thread_t * p_intf = VLCIntf;
583     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
584                                                        FIND_ANYWHERE );
585
586     if( p_playlist == NULL )
587     {
588         return NULL;
589     }
590     playlist_item_t *p_item;
591     int i;
592     BOOL b_rem = FALSE, b_dir = FALSE;
593     NSString *o_uri, *o_name;
594     NSArray *o_options;
595     NSURL *o_true_file;
596
597     /* Get the item */
598     o_uri = (NSString *)[o_one_item objectForKey: @"ITEM_URL"];
599     o_name = (NSString *)[o_one_item objectForKey: @"ITEM_NAME"];
600     o_options = (NSArray *)[o_one_item objectForKey: @"ITEM_OPTIONS"];
601
602     /* Find the name for a disc entry ( i know, can you believe the trouble?) */
603     if( ( !o_name || [o_name isEqualToString:@""] ) && [o_uri rangeOfString: @"/dev/"].location != NSNotFound )
604     {
605         int i_count, i_index;
606         struct statfs *mounts = NULL;
607
608         i_count = getmntinfo (&mounts, MNT_NOWAIT);
609         /* getmntinfo returns a pointer to static data. Do not free. */
610         for( i_index = 0 ; i_index < i_count; i_index++ )
611         {
612             NSMutableString *o_temp, *o_temp2;
613             o_temp = [NSMutableString stringWithString: o_uri];
614             o_temp2 = [NSMutableString stringWithCString: mounts[i_index].f_mntfromname];
615             [o_temp replaceOccurrencesOfString: @"/dev/rdisk" withString: @"/dev/disk" options:NULL range:NSMakeRange(0, [o_temp length]) ];
616             [o_temp2 replaceOccurrencesOfString: @"s0" withString: @"" options:NULL range:NSMakeRange(0, [o_temp2 length]) ];
617             [o_temp2 replaceOccurrencesOfString: @"s1" withString: @"" options:NULL range:NSMakeRange(0, [o_temp2 length]) ];
618
619             if( strstr( [o_temp fileSystemRepresentation], [o_temp2 fileSystemRepresentation] ) != NULL )
620             {
621                 o_name = [[NSFileManager defaultManager] displayNameAtPath: [NSString stringWithCString:mounts[i_index].f_mntonname]];
622             }
623         }
624     }
625     /* If no name, then make a guess */
626     if( !o_name) o_name = [[NSFileManager defaultManager] displayNameAtPath: o_uri];
627
628     if( [[NSFileManager defaultManager] fileExistsAtPath:o_uri isDirectory:&b_dir] && b_dir &&
629         [[NSWorkspace sharedWorkspace] getFileSystemInfoForPath: o_uri isRemovable: &b_rem
630                 isWritable:NULL isUnmountable:NULL description:NULL type:NULL] && b_rem   )
631     {
632         /* All of this is to make sure CD's play when you D&D them on VLC */
633         /* Converts mountpoint to a /dev file */
634         struct statfs *buf;
635         char *psz_dev;
636         NSMutableString *o_temp;
637
638         buf = (struct statfs *) malloc (sizeof(struct statfs));
639         statfs( [o_uri fileSystemRepresentation], buf );
640         psz_dev = strdup(buf->f_mntfromname);
641         o_temp = [NSMutableString stringWithCString: psz_dev ];
642         [o_temp replaceOccurrencesOfString: @"/dev/disk" withString: @"/dev/rdisk" options:NULL range:NSMakeRange(0, [o_temp length]) ];
643         [o_temp replaceOccurrencesOfString: @"s0" withString: @"" options:NULL range:NSMakeRange(0, [o_temp length]) ];
644         [o_temp replaceOccurrencesOfString: @"s1" withString: @"" options:NULL range:NSMakeRange(0, [o_temp length]) ];
645         o_uri = o_temp;
646     }
647
648     p_item = playlist_ItemNew( p_intf, [o_uri fileSystemRepresentation], [o_name UTF8String] );
649     if( !p_item )
650        return NULL;
651
652     if( o_options )
653     {
654         for( i = 0; i < (int)[o_options count]; i++ )
655         {
656             playlist_ItemAddOption( p_item, strdup( [[o_options objectAtIndex:i] UTF8String] ) );
657         }
658     }
659
660     /* Recent documents menu */
661     o_true_file = [NSURL fileURLWithPath: o_uri];
662     if( o_true_file != nil )
663     {
664         [[NSDocumentController sharedDocumentController]
665             noteNewRecentDocumentURL: o_true_file];
666     }
667
668     vlc_object_release( p_playlist );
669     return p_item;
670 }
671
672 - (void)appendArray:(NSArray*)o_array atPos:(int)i_position enqueue:(BOOL)b_enqueue
673 {
674     int i_item;
675     playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
676                                             FIND_ANYWHERE );
677     if( p_playlist == NULL )
678     {
679         return;
680     }
681
682     for( i_item = 0; i_item < (int)[o_array count]; i_item++ )
683     {
684         playlist_item_t *p_item;
685         NSDictionary *o_one_item;
686
687         /* Get the item */
688         o_one_item = [o_array objectAtIndex: i_item];
689         p_item = [self createItem: o_one_item];
690         if( !p_item )
691         {
692             continue;
693         }
694
695         /* Add the item */
696         playlist_AddItem( p_playlist, p_item, PLAYLIST_APPEND, i_position == -1 ? PLAYLIST_END : i_position + i_item );
697
698         if( i_item == 0 && !b_enqueue )
699         {
700             playlist_Control( p_playlist, PLAYLIST_ITEMPLAY, p_item );
701         }
702     }
703     vlc_object_release( p_playlist );
704 }
705
706 - (void)appendNodeArray:(NSArray*)o_array inNode:(playlist_item_t *)p_node atPos:(int)i_position inView:(int)i_view enqueue:(BOOL)b_enqueue
707 {
708     int i_item;
709     playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
710                                             FIND_ANYWHERE );
711     if( p_playlist == NULL )
712     {
713         return;
714     }
715
716     for( i_item = 0; i_item < (int)[o_array count]; i_item++ )
717     {
718         playlist_item_t *p_item;
719         NSDictionary *o_one_item;
720
721         /* Get the item */
722         o_one_item = [o_array objectAtIndex: i_item];
723         p_item = [self createItem: o_one_item];
724         if( !p_item )
725         {
726             continue;
727         }
728
729         /* Add the item */
730         playlist_NodeAddItem( p_playlist, p_item, i_view, p_node, PLAYLIST_APPEND, i_position + i_item );
731
732         if( i_item == 0 && !b_enqueue )
733         {
734             playlist_Control( p_playlist, PLAYLIST_ITEMPLAY, p_item );
735         }
736     }
737     vlc_object_release( p_playlist );
738
739 }
740
741 - (IBAction)handlePopUp:(id)sender
742
743 {
744     intf_thread_t * p_intf = VLCIntf;
745     vlc_value_t val1,val2;
746     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
747                                             FIND_ANYWHERE );
748     if( p_playlist == NULL )
749     {
750         return;
751     }
752
753     switch( [o_loop_popup indexOfSelectedItem] )
754     {
755         case 1:
756
757              val1.b_bool = 0;
758              var_Set( p_playlist, "loop", val1 );
759              val1.b_bool = 1;
760              var_Set( p_playlist, "repeat", val1 );
761              vout_OSDMessage( p_intf, DEFAULT_CHAN, _( "Repeat One" ) );
762         break;
763
764         case 2:
765              val1.b_bool = 0;
766              var_Set( p_playlist, "repeat", val1 );
767              val1.b_bool = 1;
768              var_Set( p_playlist, "loop", val1 );
769              vout_OSDMessage( p_intf, DEFAULT_CHAN, _( "Repeat All" ) );
770         break;
771
772         default:
773              var_Get( p_playlist, "repeat", &val1 );
774              var_Get( p_playlist, "loop", &val2 );
775              if( val1.b_bool || val2.b_bool )
776              {
777                   val1.b_bool = 0;
778                   var_Set( p_playlist, "repeat", val1 );
779                   var_Set( p_playlist, "loop", val1 );
780                   vout_OSDMessage( p_intf, DEFAULT_CHAN, _( "Repeat Off" ) );
781              }
782          break;
783      }
784      vlc_object_release( p_playlist );
785      [self playlistUpdated];
786 }
787
788 - (NSMutableArray *)subSearchItem:(playlist_item_t *)p_item
789 {
790     playlist_t *p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
791                                                        FIND_ANYWHERE );
792     playlist_item_t *p_selected_item;
793     int i_current, i_selected_row;
794
795     if( !p_playlist )
796         return NULL;
797
798     i_selected_row = [o_outline_view selectedRow];
799     if (i_selected_row < 0)
800         i_selected_row = 0;
801
802     p_selected_item = (playlist_item_t *)[[o_outline_view itemAtRow:
803                                             i_selected_row] pointerValue];
804
805     for( i_current = 0; i_current < p_item->i_children ; i_current++ )
806     {
807         char *psz_temp;
808         NSString *o_current_name, *o_current_author;
809
810         vlc_mutex_lock( &p_playlist->object_lock );
811         o_current_name = [NSString stringWithUTF8String:
812             p_item->pp_children[i_current]->input.psz_name];
813         psz_temp = vlc_input_item_GetInfo( &p_item->input ,
814                                    _("Meta-information"),_("Artist") );
815         o_current_author = [NSString stringWithUTF8String: psz_temp];
816         free( psz_temp);
817         vlc_mutex_unlock( &p_playlist->object_lock );
818
819         if( p_selected_item == p_item->pp_children[i_current] &&
820                     b_selected_item_met == NO )
821         {
822             b_selected_item_met = YES;
823         }
824         else if( p_selected_item == p_item->pp_children[i_current] &&
825                     b_selected_item_met == YES )
826         {
827             vlc_object_release( p_playlist );
828             return NULL;
829         }
830         else if( b_selected_item_met == YES &&
831                     ( [o_current_name rangeOfString:[o_search_field
832                         stringValue] options:NSCaseInsensitiveSearch ].length ||
833                       [o_current_author rangeOfString:[o_search_field
834                         stringValue] options:NSCaseInsensitiveSearch ].length ) )
835         {
836             vlc_object_release( p_playlist );
837             /*Adds the parent items in the result array as well, so that we can
838             expand the tree*/
839             return [NSMutableArray arrayWithObject: [NSValue
840                             valueWithPointer: p_item->pp_children[i_current]]];
841         }
842         if( p_item->pp_children[i_current]->i_children > 0 )
843         {
844             id o_result = [self subSearchItem:
845                                             p_item->pp_children[i_current]];
846             if( o_result != NULL )
847             {
848                 vlc_object_release( p_playlist );
849                 [o_result insertObject: [NSValue valueWithPointer:
850                                 p_item->pp_children[i_current]] atIndex:0];
851                 return o_result;
852             }
853         }
854     }
855     vlc_object_release( p_playlist );
856     return NULL;
857 }
858
859 - (IBAction)searchItem:(id)sender
860 {
861     playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
862                                                        FIND_ANYWHERE );
863     playlist_view_t * p_view;
864     id o_result;
865
866     unsigned int i;
867     int i_row = -1;
868
869     b_selected_item_met = NO;
870
871     if( p_playlist == NULL )
872         return;
873     p_view = playlist_ViewFind( p_playlist, i_current_view );
874
875     if( p_view )
876     {
877         /*First, only search after the selected item:*
878          *(b_selected_item_met = NO)                 */
879         o_result = [self subSearchItem:p_view->p_root];
880         if( o_result == NULL )
881         {
882             /* If the first search failed, search again from the beginning */
883             o_result = [self subSearchItem:p_view->p_root];
884         }
885         if( o_result != NULL )
886         {
887             for( i = 0 ; i < [o_result count] - 1 ; i++ )
888             {
889                 [o_outline_view expandItem: [o_outline_dict objectForKey:
890                             [NSString stringWithFormat: @"%p",
891                             [[o_result objectAtIndex: i] pointerValue]]]];
892             }
893             i_row = [o_outline_view rowForItem: [o_outline_dict objectForKey:
894                             [NSString stringWithFormat: @"%p",
895                             [[o_result objectAtIndex: [o_result count] - 1 ]
896                             pointerValue]]]];
897         }
898         if( i_row > -1 )
899         {
900             [o_outline_view selectRow:i_row byExtendingSelection: NO];
901             [o_outline_view scrollRowToVisible: i_row];
902         }
903     }
904     vlc_object_release( p_playlist );
905 }
906
907 - (NSMenu *)menuForEvent:(NSEvent *)o_event
908 {
909     NSPoint pt;
910     vlc_bool_t b_rows;
911     vlc_bool_t b_item_sel;
912
913     pt = [o_outline_view convertPoint: [o_event locationInWindow]
914                                                  fromView: nil];
915     b_item_sel = ( [o_outline_view rowAtPoint: pt] != -1 &&
916                    [o_outline_view selectedRow] != -1 );
917     b_rows = [o_outline_view numberOfRows] != 0;
918
919     [o_mi_play setEnabled: b_item_sel];
920     [o_mi_delete setEnabled: b_item_sel];
921     [o_mi_selectall setEnabled: b_rows];
922     [o_mi_info setEnabled: b_item_sel];
923
924     return( o_ctx_menu );
925 }
926
927 - (playlist_item_t *)selectedPlaylistItem
928 {
929     return [[o_outline_view itemAtRow: [o_outline_view selectedRow]]
930                                                                 pointerValue];
931 }
932
933 - (void)outlineView: (NSTableView*)o_tv
934                   didClickTableColumn:(NSTableColumn *)o_tc
935 {
936     int i_mode = 0, i_type;
937     intf_thread_t *p_intf = VLCIntf;
938     playlist_view_t *p_view;
939
940     playlist_t *p_playlist = (playlist_t *)vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
941                                        FIND_ANYWHERE );
942     if( p_playlist == NULL )
943     {
944         return;
945     }
946     
947     /* Check whether the selected table column header corresponds to a
948        sortable table column*/
949     if( !( o_tc == o_tc_name || o_tc == o_tc_author ) )
950     {
951         vlc_object_release( p_playlist );
952         return;
953     }
954
955     p_view = playlist_ViewFind( p_playlist, i_current_view );
956
957     if( o_tc_sortColumn == o_tc )
958     {
959         b_isSortDescending = !b_isSortDescending;
960     }
961     else
962     {
963         b_isSortDescending = VLC_FALSE;
964     }
965
966     if( o_tc == o_tc_name )
967     {
968         i_mode = SORT_TITLE;
969     }
970     else if( o_tc == o_tc_author )
971     {
972         i_mode = SORT_AUTHOR;
973     }
974
975     if( b_isSortDescending )
976     {
977         i_type = ORDER_REVERSE;
978     }
979     else
980     {
981         i_type = ORDER_NORMAL;
982     }
983
984     vlc_mutex_lock( &p_playlist->object_lock );
985     playlist_RecursiveNodeSort( p_playlist, p_view->p_root, i_mode, i_type );
986     vlc_mutex_unlock( &p_playlist->object_lock );
987
988     vlc_object_release( p_playlist );
989     [self playlistUpdated];
990
991     o_tc_sortColumn = o_tc;
992     [o_outline_view setHighlightedTableColumn:o_tc];
993
994     if( b_isSortDescending )
995     {
996         [o_outline_view setIndicatorImage:o_descendingSortingImage
997                                                         inTableColumn:o_tc];
998     }
999     else
1000     {
1001         [o_outline_view setIndicatorImage:o_ascendingSortingImage
1002                                                         inTableColumn:o_tc];
1003     }
1004 }
1005
1006
1007 - (void)outlineView:(NSOutlineView *)outlineView
1008                                 willDisplayCell:(id)cell
1009                                 forTableColumn:(NSTableColumn *)tableColumn
1010                                 item:(id)item
1011 {
1012     playlist_t *p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
1013                                           FIND_ANYWHERE );
1014     playlist_item_t *p_item = (playlist_item_t *)[item pointerValue];
1015
1016     if( !p_playlist ) return;
1017
1018     if( ( p_item == p_playlist->status.p_item ) ||
1019             ( p_item->i_children != 0 &&
1020             [self isItem: p_playlist->status.p_item inNode: p_item] ) )
1021     {
1022         [cell setFont: [NSFont boldSystemFontOfSize: 0]];
1023     }
1024     else
1025     {
1026         [cell setFont: [NSFont systemFontOfSize: 0]];
1027     }
1028     vlc_object_release( p_playlist );
1029 }
1030
1031 @end
1032
1033 @implementation VLCPlaylist (NSOutlineViewDataSource)
1034
1035 /* return the number of children for Obj-C pointer item */ /* DONE */
1036 - (int)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
1037 {
1038     int i_return = 0;
1039     playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
1040                                                        FIND_ANYWHERE );
1041     if( p_playlist == NULL || outlineView != o_outline_view )
1042         return 0;
1043
1044     if( item == nil )
1045     {
1046         /* root object */
1047         playlist_view_t *p_view;
1048         p_view = playlist_ViewFind( p_playlist, i_current_view );
1049         if( p_view && p_view->p_root )
1050             i_return = p_view->p_root->i_children;
1051     }
1052     else
1053     {
1054         playlist_item_t *p_item = (playlist_item_t *)[item pointerValue];
1055         if( p_item )
1056             i_return = p_item->i_children;
1057     }
1058     vlc_object_release( p_playlist );
1059     
1060     if( i_return <= 0 )
1061         i_return = 0;
1062     
1063     return i_return;
1064 }
1065
1066 /* return the child at index for the Obj-C pointer item */ /* DONE */
1067 - (id)outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item
1068 {
1069     playlist_item_t *p_return = NULL;
1070     playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
1071                                                        FIND_ANYWHERE );
1072     NSValue *o_value;
1073
1074     if( p_playlist == NULL )
1075         return nil;
1076
1077     if( item == nil )
1078     {
1079         /* root object */
1080         playlist_view_t *p_view;
1081         p_view = playlist_ViewFind( p_playlist, i_current_view );
1082         if( p_view && index < p_view->p_root->i_children && index >= 0 )
1083             p_return = p_view->p_root->pp_children[index];
1084     }
1085     else
1086     {
1087         playlist_item_t *p_item = (playlist_item_t *)[item pointerValue];
1088         if( p_item && index < p_item->i_children && index >= 0 )
1089             p_return = p_item->pp_children[index];
1090     }
1091     
1092     if( p_playlist->i_size >= 2 )
1093     {
1094         [o_status_field setStringValue: [NSString stringWithFormat:
1095                     _NS("%i items in playlist"), p_playlist->i_size]];
1096     }
1097     else
1098     {
1099         if( p_playlist->i_size == 0 )
1100         {
1101             [o_status_field setStringValue: [NSString stringWithFormat:
1102                     _NS("no items in playlist"), p_playlist->i_size]];
1103         }
1104         else
1105         {
1106             [o_status_field setStringValue: [NSString stringWithFormat:
1107                     _NS("1 item in playlist"), p_playlist->i_size]];
1108         }
1109     }
1110
1111     vlc_object_release( p_playlist );
1112
1113     o_value = [[NSValue valueWithPointer: p_return] retain];
1114
1115     [o_outline_dict setObject:o_value forKey:[NSString stringWithFormat:@"%p", p_return]];
1116     return o_value;
1117 }
1118
1119 /* is the item expandable */
1120 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
1121 {
1122     int i_return = 0;
1123     playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
1124                                                        FIND_ANYWHERE );
1125     if( p_playlist == NULL )
1126         return NO;
1127
1128     if( item == nil )
1129     {
1130         /* root object */
1131         playlist_view_t *p_view;
1132         p_view = playlist_ViewFind( p_playlist, i_current_view );
1133         if( p_view && p_view->p_root )
1134             i_return = p_view->p_root->i_children;
1135     }
1136     else
1137     {
1138         playlist_item_t *p_item = (playlist_item_t *)[item pointerValue];
1139         if( p_item )
1140             i_return = p_item->i_children;
1141     }
1142     vlc_object_release( p_playlist );
1143
1144     if( i_return <= 0 )
1145         return NO;
1146     else
1147         return YES;
1148 }
1149
1150 /* retrieve the string values for the cells */
1151 - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)o_tc byItem:(id)item
1152 {
1153     id o_value = nil;
1154     intf_thread_t *p_intf = VLCIntf;
1155     playlist_t *p_playlist;
1156     playlist_item_t *p_item;
1157     
1158     if( item == nil || ![item isKindOfClass: [NSValue class]] ) return( @"error" );
1159     
1160     p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
1161                                                FIND_ANYWHERE );
1162     if( p_playlist == NULL )
1163     {
1164         return( @"error" );
1165     }
1166
1167     p_item = (playlist_item_t *)[item pointerValue];
1168
1169     if( p_item == NULL )
1170     {
1171         vlc_object_release( p_playlist );
1172         return( @"error");
1173     }
1174
1175     if( [[o_tc identifier] isEqualToString:@"1"] )
1176     {
1177         o_value = [NSString stringWithUTF8String:
1178             p_item->input.psz_name];
1179         if( o_value == NULL )
1180             o_value = [NSString stringWithCString:
1181                 p_item->input.psz_name];
1182     }
1183     else if( [[o_tc identifier] isEqualToString:@"2"] )
1184     {
1185         char *psz_temp;
1186         psz_temp = vlc_input_item_GetInfo( &p_item->input ,_("Meta-information"),_("Artist") );
1187
1188         if( psz_temp == NULL )
1189             o_value = @"";
1190         else
1191         {
1192             o_value = [NSString stringWithUTF8String: psz_temp];
1193             if( o_value == NULL )
1194             {
1195                 o_value = [NSString stringWithCString: psz_temp];
1196             }
1197             free( psz_temp );
1198         }
1199     }
1200     else if( [[o_tc identifier] isEqualToString:@"3"] )
1201     {
1202         char psz_duration[MSTRTIME_MAX_SIZE];
1203         mtime_t dur = p_item->input.i_duration;
1204         if( dur != -1 )
1205         {
1206             secstotimestr( psz_duration, dur/1000000 );
1207             o_value = [NSString stringWithUTF8String: psz_duration];
1208         }
1209         else
1210         {
1211             o_value = @"-:--:--";
1212         }
1213     }
1214     vlc_object_release( p_playlist );
1215
1216     return( o_value );
1217 }
1218
1219 /* Required for drag & drop and reordering */
1220 - (BOOL)outlineView:(NSOutlineView *)outlineView writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pboard
1221 {
1222 /*    unsigned int i;
1223
1224     for( i = 0 ; i < [items count] ; i++ )
1225     {
1226         if( [outlineView levelForItem: [items objectAtIndex: i]] == 0 )
1227         {
1228             return NO;
1229         }
1230     }*/
1231     return NO;
1232 }
1233
1234 - (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id <NSDraggingInfo>)info proposedItem:(id)item proposedChildIndex:(int)index
1235 {
1236     NSPasteboard *o_pasteboard = [info draggingPasteboard];
1237
1238     if( [[o_pasteboard types] containsObject: NSFilenamesPboardType] )
1239     {
1240         return NSDragOperationGeneric;
1241     }
1242     return NSDragOperationNone;
1243 }
1244
1245 - (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id <NSDraggingInfo>)info item:(id)item childIndex:(int)index
1246 {
1247     playlist_t * p_playlist =  vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
1248                                                        FIND_ANYWHERE );
1249     NSPasteboard *o_pasteboard = [info draggingPasteboard];
1250
1251     if( !p_playlist ) return NO;
1252
1253     if( [[o_pasteboard types] containsObject: NSFilenamesPboardType] )
1254     {
1255         int i;
1256         playlist_item_t *p_node = [item pointerValue];
1257
1258         NSArray *o_array = [NSArray array];
1259         NSArray *o_values = [[o_pasteboard propertyListForType:
1260                                         NSFilenamesPboardType]
1261                                 sortedArrayUsingSelector:
1262                                         @selector(caseInsensitiveCompare:)];
1263
1264         for( i = 0; i < (int)[o_values count]; i++)
1265         {
1266             NSDictionary *o_dic;
1267             o_dic = [NSDictionary dictionaryWithObject:[o_values
1268                         objectAtIndex:i] forKey:@"ITEM_URL"];
1269             o_array = [o_array arrayByAddingObject: o_dic];
1270         }
1271
1272         if ( item == nil )
1273         {
1274             [self appendArray: o_array atPos: index enqueue: YES];
1275         }
1276         else if( p_node->i_children == -1 )
1277         {
1278             int i_counter;
1279             playlist_item_t *p_real_node = NULL;
1280
1281             for( i_counter = 0 ; i_counter < p_node->i_parents ; i_counter++ )
1282             {
1283                 if( p_node->pp_parents[i_counter]->i_view == i_current_view )
1284                 {
1285                     p_real_node = p_node->pp_parents[i_counter]->p_parent;
1286                     break;
1287                 }
1288                 if( i_counter == p_node->i_parents )
1289                 {
1290                     return NO;
1291                 }
1292             }
1293             [self appendNodeArray: o_array inNode: p_real_node
1294                 atPos: index inView: i_current_view enqueue: YES];
1295         }
1296         else
1297         {
1298             [self appendNodeArray: o_array inNode: p_node
1299                 atPos: index inView: i_current_view enqueue: YES];
1300         }
1301         vlc_object_release( p_playlist );
1302         return YES;
1303     }
1304     vlc_object_release( p_playlist );
1305     return NO;
1306 }
1307
1308 /* Delegate method of NSWindow */
1309 /*- (void)windowWillClose:(NSNotification *)aNotification
1310 {
1311     [o_btn_playlist setState: NSOffState];
1312 }
1313 */
1314 @end
1315
1316