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