]> git.sesse.net Git - vlc/blob - modules/gui/macosx/playlist.m
* extras/MacOSX/Resources/English.lproj/MainMenu.nib
[vlc] / modules / gui / macosx / playlist.m
1 /*****************************************************************************
2  * playlist.m: MacOS X interface plugin
3  *****************************************************************************
4  * Copyright (C) 2002-2004 VideoLAN
5  * $Id: playlist.m,v 1.51 2004/01/09 22:11:04 hartman Exp $
6  *
7  * Authors: Jon Lech Johansen <jon-vl@nanocrew.net>
8  *          Derk-Jan Hartman <hartman at videolan dot org>
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  * 
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
23  *****************************************************************************/
24
25 /*****************************************************************************
26  * Preamble
27  *****************************************************************************/
28 #include <stdlib.h>                                      /* malloc(), free() */
29 #include <sys/param.h>                                    /* for MAXPATHLEN */
30 #include <string.h>
31 #include <math.h>
32 #include <sys/mount.h>
33 #include <vlc_keys.h>
34
35 #include "intf.h"
36 #include "playlist.h"
37 #include "controls.h"
38
39 /*****************************************************************************
40  * VLCPlaylistView implementation 
41  *****************************************************************************/
42 @implementation VLCPlaylistView
43
44 - (NSMenu *)menuForEvent:(NSEvent *)o_event
45 {
46     return( [[self delegate] menuForEvent: o_event] );
47 }
48
49 - (void)keyDown:(NSEvent *)o_event
50 {
51     unichar key = 0;
52     int i, c, i_row;
53     NSMutableArray *o_to_delete;
54     NSNumber *o_number;
55     
56     playlist_t * p_playlist;
57     intf_thread_t * p_intf = [NSApp getIntf];
58
59     if( [[o_event characters] length] )
60     {
61         key = [[o_event characters] characterAtIndex: 0];
62     }
63
64     p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
65                                           FIND_ANYWHERE );
66     
67     if ( p_playlist == NULL )
68     {
69         return;
70     }
71     
72     switch( key )
73     {
74         case NSDeleteCharacter:
75         case NSDeleteFunctionKey:
76         case NSDeleteCharFunctionKey:
77         case NSBackspaceCharacter:
78             o_to_delete = [NSMutableArray arrayWithArray:[[self selectedRowEnumerator] allObjects]];
79             c = [o_to_delete count];
80             
81             for( i = 0; i < c; i++ ) {
82                 o_number = [o_to_delete lastObject];
83                 i_row = [o_number intValue];
84                 
85                 if( p_playlist->i_index == i_row && p_playlist->i_status )
86                 {
87                     playlist_Stop( p_playlist );
88                 }
89                 [o_to_delete removeObject: o_number];
90                 [self deselectRow: i_row];
91                 playlist_Delete( p_playlist, i_row );
92             }
93             [self reloadData];
94             break;
95             
96         default:
97             [super keyDown: o_event];
98             break;
99     }
100
101     if( p_playlist != NULL )
102     {
103         vlc_object_release( p_playlist );
104     }
105 }
106
107 @end
108
109 /*****************************************************************************
110  * VLCPlaylist implementation 
111  *****************************************************************************/
112 @implementation VLCPlaylist
113
114 - (id)init
115 {
116     self = [super init];
117     if ( self !=nil )
118     {
119         i_moveRow = -1;
120     }
121     return self;
122 }
123
124 - (void)awakeFromNib
125 {
126     [o_table_view setTarget: self];
127     [o_table_view setDelegate: self];
128     [o_table_view setDataSource: self];
129
130     [o_table_view setDoubleAction: @selector(playItem:)];
131
132     [o_table_view registerForDraggedTypes: 
133         [NSArray arrayWithObjects: NSFilenamesPboardType, nil]];
134
135     [o_window setExcludedFromWindowsMenu: TRUE];
136     [self initStrings];
137 }
138
139 - (void)initStrings
140 {
141     [o_window setTitle: _NS("Playlist")];
142     [o_mi_save_playlist setTitle: _NS("Save Playlist...")];
143     [o_mi_play setTitle: _NS("Play")];
144     [o_mi_delete setTitle: _NS("Delete")];
145     [o_mi_selectall setTitle: _NS("Select All")];
146     [[o_tc_name headerCell] setStringValue:_NS("Name")];
147     [[o_tc_author headerCell] setStringValue:_NS("Author")];
148     [[o_tc_duration headerCell] setStringValue:_NS("Duration")];
149     [o_random_ckb setTitle: _NS("Shuffle")];
150     [o_loop_ckb setTitle: _NS("Repeat Playlist")];
151     [o_repeat_ckb setTitle: _NS("Repeat Item")];
152     [o_search_button setTitle: _NS("Search")];
153     [o_btn_playlist setToolTip: _NS("Playlist")];
154 }
155
156 - (BOOL)tableView:(NSTableView *)o_tv 
157                   shouldEditTableColumn:(NSTableColumn *)o_tc
158                   row:(int)i_row
159 {
160     return( NO );
161 }
162
163 - (NSMenu *)menuForEvent:(NSEvent *)o_event
164 {
165     NSPoint pt;
166     vlc_bool_t b_rows;
167     vlc_bool_t b_item_sel;
168
169     pt = [o_table_view convertPoint: [o_event locationInWindow] 
170                                                  fromView: nil];
171     b_item_sel = ( [o_table_view rowAtPoint: pt] != -1 &&
172                    [o_table_view selectedRow] != -1 );
173     b_rows = [o_table_view numberOfRows] != 0;
174
175     [o_mi_play setEnabled: b_item_sel];
176     [o_mi_delete setEnabled: b_item_sel];
177     [o_mi_selectall setEnabled: b_rows];
178
179     return( o_ctx_menu );
180 }
181
182 - (IBAction)toggleWindow:(id)sender
183 {
184     if( [o_window isVisible] )
185     {
186         [o_window orderOut:sender];
187         [o_btn_playlist setState:NSOffState];
188     }
189     else
190     {
191         [o_window makeKeyAndOrderFront:sender];
192         [o_btn_playlist setState:NSOnState];
193     }
194 }
195
196 - (IBAction)savePlaylist:(id)sender
197 {
198     intf_thread_t * p_intf = [NSApp getIntf];
199     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
200                                                        FIND_ANYWHERE );
201     
202     NSSavePanel *o_save_panel = [NSSavePanel savePanel];
203     NSString * o_name = [NSString stringWithFormat: @"%@.m3u", _NS("Untitled")];
204     [o_save_panel setTitle: _NS("Save Playlist")];
205     [o_save_panel setPrompt: _NS("Save")];
206
207     if( [o_save_panel runModalForDirectory: nil
208             file: o_name] == NSOKButton )
209     {
210         playlist_SaveFile( p_playlist, [[o_save_panel filename] fileSystemRepresentation] );
211     }
212
213 }
214
215 - (IBAction)playItem:(id)sender
216 {
217     intf_thread_t * p_intf = [NSApp getIntf];
218     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
219                                                        FIND_ANYWHERE );
220
221     if( p_playlist != NULL )
222     {
223         playlist_Goto( p_playlist, [o_table_view selectedRow] );
224         vlc_object_release( p_playlist );
225     }
226 }
227
228 - (IBAction)deleteItems:(id)sender
229 {
230     int i, c, i_row;
231     NSMutableArray *o_to_delete;
232     NSNumber *o_number;
233
234     intf_thread_t * p_intf = [NSApp getIntf];
235     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
236                                                        FIND_ANYWHERE );
237
238     if( p_playlist == NULL )
239     {
240         return;
241     }
242     
243     o_to_delete = [NSMutableArray arrayWithArray:[[o_table_view selectedRowEnumerator] allObjects]];
244     c = (int)[o_to_delete count];
245     
246     for( i = 0; i < c; i++ ) {
247         o_number = [o_to_delete lastObject];
248         i_row = [o_number intValue];
249         
250         if( p_playlist->i_index == i_row && p_playlist->i_status )
251         {
252             playlist_Stop( p_playlist );
253         }
254         [o_to_delete removeObject: o_number];
255         [o_table_view deselectRow: i_row];
256         playlist_Delete( p_playlist, i_row );
257     }
258
259     vlc_object_release( p_playlist );
260
261     /* this is actually duplicity, because the intf.m manage also updates the view
262      * when the playlist changes. we do this on purpose, because else there is a 
263      * delay of .5 sec or so when we delete an item */
264     [self playlistUpdated];
265     [self updateRowSelection];
266 }
267
268 - (IBAction)selectAll:(id)sender
269 {
270     [o_table_view selectAll: nil];
271 }
272
273
274 - (IBAction)searchItem:(id)sender
275 {
276     int i_current = -1;
277     NSString *o_current_name;
278     NSString *o_current_author;
279
280     intf_thread_t * p_intf = [NSApp getIntf];
281     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
282                                                FIND_ANYWHERE );
283     
284     if( p_playlist == NULL )
285     {
286         return;
287     }
288     if( [o_table_view numberOfRows] < 1 )
289     {
290         return;
291     }
292
293     if( [o_table_view selectedRow] == [o_table_view numberOfRows]-1 )
294     {
295         i_current = -1;
296     }
297     else
298     {
299         i_current = [o_table_view selectedRow]; 
300     }
301
302     do
303     {
304         i_current++;
305
306         vlc_mutex_lock( &p_playlist->object_lock );
307         o_current_name = [NSString stringWithUTF8String: 
308             p_playlist->pp_items[i_current]->psz_name];
309         o_current_author = [NSString stringWithUTF8String: 
310             playlist_GetInfo(p_playlist, i_current ,_("General"),_("Author") )];
311         vlc_mutex_unlock( &p_playlist->object_lock );
312
313
314         if( [o_current_name rangeOfString:[o_search_keyword stringValue] options:NSCaseInsensitiveSearch ].length ||
315              [o_current_author rangeOfString:[o_search_keyword stringValue] options:NSCaseInsensitiveSearch ].length )
316         {
317              [o_table_view selectRow: i_current byExtendingSelection: NO];
318              [o_table_view scrollRowToVisible: i_current];
319              break;
320         }
321         if( i_current == [o_table_view numberOfRows] - 1 )
322         {
323              i_current = -1;
324         }
325     }
326     while (i_current != [o_table_view selectedRow]);
327     vlc_object_release( p_playlist );
328 }
329
330 - (void)appendArray:(NSArray*)o_array atPos:(int)i_position enqueue:(BOOL)b_enqueue
331 {
332     int i_item;
333     intf_thread_t * p_intf = [NSApp getIntf];
334     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
335                                                        FIND_ANYWHERE );
336
337     if( p_playlist == NULL )
338     {
339         return;
340     }
341
342     for ( i_item = 0; i_item < (int)[o_array count]; i_item++ )
343     {
344         /* One item */
345         NSDictionary *o_one_item;
346         int j, i_new_position = -1;
347         int i_mode = PLAYLIST_INSERT;
348         BOOL b_rem = FALSE, b_dir = FALSE;
349         NSString *o_uri, *o_name;
350         NSArray *o_options;
351         NSURL *o_true_file;
352     
353         /* Get the item */
354         o_one_item = [o_array objectAtIndex: i_item];
355         o_uri = (NSString *)[o_one_item objectForKey: @"ITEM_URL"];
356         o_name = (NSString *)[o_one_item objectForKey: @"ITEM_NAME"];
357         o_options = (NSArray *)[o_one_item objectForKey: @"ITEM_OPTIONS"];
358         
359         /* If no name, then make a guess */
360         if( !o_name) o_name = [[NSFileManager defaultManager] displayNameAtPath: o_uri];
361     
362         if( [[NSFileManager defaultManager] fileExistsAtPath:o_uri isDirectory:&b_dir] && b_dir &&
363             [[NSWorkspace sharedWorkspace] getFileSystemInfoForPath: o_uri isRemovable: &b_rem
364                     isWritable:NULL isUnmountable:NULL description:NULL type:NULL] && b_rem   )
365         {
366             /* All of this is to make sure CD's play when you D&D them on VLC */
367             /* Converts mountpoint to a /dev file */
368             struct statfs *buf;
369             char *psz_dev, *temp;
370             buf = (struct statfs *) malloc (sizeof(struct statfs));
371             statfs( [o_uri fileSystemRepresentation], buf );
372             psz_dev = strdup(buf->f_mntfromname);
373             free( buf );
374             temp = strrchr( psz_dev , 's' );
375             psz_dev[temp - psz_dev] = '\0';
376             o_uri = [NSString stringWithCString: psz_dev ];
377         }
378     
379         if (i_item == 0 && !b_enqueue)
380             i_mode |= PLAYLIST_GO;
381         
382         /* Add the item */
383         i_new_position = playlist_Add( p_playlist, [o_uri fileSystemRepresentation], 
384                       [o_name UTF8String], i_mode, 
385                       i_position == -1 ? PLAYLIST_END : i_position + i_item);
386         
387         /* Add the options, when there are any */
388         if( o_options )
389         {
390             for( j = 0; j < [o_options count]; j++ )
391             {
392                 playlist_AddOption( p_playlist, i_new_position,
393                  strdup( [[o_options objectAtIndex:j] UTF8String] ) );
394             }
395         }
396     
397         /* Recent documents menu */
398         o_true_file = [NSURL fileURLWithPath: o_uri];
399         if( o_true_file != nil )
400         { 
401             [[NSDocumentController sharedDocumentController]
402                 noteNewRecentDocumentURL: o_true_file]; 
403         }
404     }
405
406     vlc_object_release( p_playlist );
407 }
408
409 - (void)playlistUpdated
410 {
411     vlc_value_t val;
412     intf_thread_t * p_intf = [NSApp getIntf];
413     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
414                                                        FIND_ANYWHERE );
415     if( p_playlist != NULL )
416     {
417         var_Get( p_playlist, "random", &val );
418         [o_random_ckb setState: val.b_bool];
419
420         var_Get( p_playlist, "loop", &val );
421         [o_loop_ckb setState: val.b_bool];
422
423         var_Get( p_playlist, "repeat", &val );
424         [o_repeat_ckb setState: val.b_bool];
425
426         vlc_object_release( p_playlist );
427     }
428     [o_table_view reloadData];
429 }
430
431 - (void)updateRowSelection
432 {
433     int i_row;
434
435     intf_thread_t * p_intf = [NSApp getIntf];
436     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
437                                                        FIND_ANYWHERE );
438
439     if( p_playlist == NULL )
440     {
441         return;
442     }
443
444     i_row = p_playlist->i_index;
445     vlc_object_release( p_playlist );
446
447     [o_table_view selectRow: i_row byExtendingSelection: NO];
448     [o_table_view scrollRowToVisible: i_row];
449 }
450     
451
452 @end
453
454 @implementation VLCPlaylist (NSTableDataSource)
455
456 - (int)numberOfRowsInTableView:(NSTableView *)o_tv
457 {
458     int i_count = 0;
459     intf_thread_t * p_intf = [NSApp getIntf];
460     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
461                                                        FIND_ANYWHERE );
462
463     if( p_playlist != NULL )
464     {
465         vlc_mutex_lock( &p_playlist->object_lock );
466         i_count = p_playlist->i_size;
467         vlc_mutex_unlock( &p_playlist->object_lock );
468         vlc_object_release( p_playlist );
469     }
470     [o_status_field setStringValue: [NSString stringWithFormat:_NS("%i items in playlist"), i_count]];
471     return( i_count );
472 }
473
474 - (id)tableView:(NSTableView *)o_tv 
475                 objectValueForTableColumn:(NSTableColumn *)o_tc 
476                 row:(int)i_row
477 {
478     id o_value = nil;
479     intf_thread_t * p_intf = [NSApp getIntf];
480     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
481                                                FIND_ANYWHERE );
482
483     if( p_playlist == NULL )
484     {
485         return( nil );
486     }
487
488     if( [[o_tc identifier] isEqualToString:@"0"] )
489     {
490         o_value = [NSString stringWithFormat:@"%i", i_row + 1];
491     }
492     else if( [[o_tc identifier] isEqualToString:@"1"] )
493     {
494         vlc_mutex_lock( &p_playlist->object_lock );
495         o_value = [[NSString stringWithUTF8String: 
496             p_playlist->pp_items[i_row]->psz_name] lastPathComponent];
497         vlc_mutex_unlock( &p_playlist->object_lock );
498     }
499     else if( [[o_tc identifier] isEqualToString:@"2"] )
500     {
501         vlc_mutex_lock( &p_playlist->object_lock );
502         o_value = [NSString stringWithUTF8String: 
503             playlist_GetInfo(p_playlist, i_row ,_("General"),_("Author") )];
504         vlc_mutex_unlock( &p_playlist->object_lock );
505     }
506     else if( [[o_tc identifier] isEqualToString:@"3"] )
507     {
508         char psz_duration[MSTRTIME_MAX_SIZE];
509         mtime_t dur = p_playlist->pp_items[i_row]->i_duration;
510         if( dur != -1 )
511         {
512             secstotimestr( psz_duration, dur/1000000 );
513             o_value = [NSString stringWithUTF8String: psz_duration];
514         }
515         else
516         {
517             o_value = @"-:--:--";
518         }
519     }
520
521     vlc_object_release( p_playlist );
522
523     return( o_value );
524 }
525
526 - (BOOL)tableView:(NSTableView *)o_tv
527                     writeRows:(NSArray*)o_rows
528                     toPasteboard:(NSPasteboard*)o_pasteboard 
529 {
530     int i_rows = [o_rows count];
531     NSArray *o_filenames = [NSArray array];
532     
533     [o_pasteboard declareTypes:[NSArray arrayWithObject:NSFilenamesPboardType] owner:self];
534     [o_pasteboard setPropertyList:o_filenames forType:NSFilenamesPboardType];
535     if ( i_rows == 1 )
536     {
537         i_moveRow = [[o_rows objectAtIndex:0]intValue];
538         return YES;
539     }
540     return NO;
541 }
542
543 - (NSDragOperation)tableView:(NSTableView*)o_tv
544                     validateDrop:(id <NSDraggingInfo>)o_info
545                     proposedRow:(int)i_row
546                     proposedDropOperation:(NSTableViewDropOperation)o_operation 
547 {
548     if ( o_operation == NSTableViewDropAbove )
549     {
550         if ( i_moveRow >= 0 )
551         {
552             if ( i_row != i_moveRow )
553             {
554                 return NSDragOperationMove;
555             }
556             /* what if in the previous run, the row wasn't actually moved? 
557                then we can't drop new files on this location */
558             return NSDragOperationNone;
559         }
560         return NSDragOperationGeneric;
561     }
562     return NSDragOperationNone;
563 }
564
565 - (BOOL)tableView:(NSTableView*)o_tv
566                     acceptDrop:(id <NSDraggingInfo>)o_info
567                     row:(int)i_proposed_row
568                     dropOperation:(NSTableViewDropOperation)o_operation 
569 {
570     if (  i_moveRow >= 0 )
571     {
572         if (i_moveRow != -1 && i_proposed_row != -1)
573         {
574             intf_thread_t * p_intf = [NSApp getIntf];
575             playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
576                                                             FIND_ANYWHERE );
577         
578             if( p_playlist == NULL )
579             {
580                 i_moveRow = -1;
581                 return NO;
582             }
583     
584             playlist_Move( p_playlist, i_moveRow, i_proposed_row ); 
585         
586             vlc_object_release( p_playlist );
587         }
588         [self playlistUpdated];
589         i_moveRow = -1;
590         return YES;
591     }
592     else
593     {
594         NSPasteboard * o_pasteboard;
595         o_pasteboard = [o_info draggingPasteboard];
596         
597         if( [[o_pasteboard types] containsObject: NSFilenamesPboardType] )
598         {
599             int i;
600             NSArray *o_array = [NSArray array];
601             NSArray *o_values = [[o_pasteboard propertyListForType: NSFilenamesPboardType]
602                         sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
603
604             for( i = 0; i < (int)[o_values count]; i++)
605             {
606                 NSDictionary *o_dic;
607                 o_dic = [NSDictionary dictionaryWithObject:[o_values objectAtIndex:i] forKey:@"ITEM_URL"];
608                 o_array = [o_array arrayByAddingObject: o_dic];
609             }
610             [self appendArray: o_array atPos: i_proposed_row enqueue:YES];
611             return YES;
612         }
613         return NO;
614     }
615     [self updateRowSelection];
616 }
617
618 /* Delegate method of NSWindow */
619 - (void)windowWillClose:(NSNotification *)aNotification
620 {
621     [o_btn_playlist setState: NSOffState];
622 }
623
624 @end
625