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