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