]> git.sesse.net Git - vlc/blob - modules/gui/macosx/playlist.m
Checkboxes stat at startup, in the playlist, now reflect the state set ine the prefs...
[vlc] / modules / gui / macosx / playlist.m
1 /*****************************************************************************
2  * playlist.m: MacOS X interface plugin
3  *****************************************************************************
4  * Copyright (C) 2002-2003 VideoLAN
5  * $Id: playlist.m,v 1.41 2003/11/17 14:11:05 bigben Exp $
6  *
7  * Authors: Jon Lech Johansen <jon-vl@nanocrew.net>
8  *          Derk-Jan Hartman <thedj@users.sourceforge.net>
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
137     [o_window setTitle: _NS("Playlist")];
138     [o_mi_save_playlist setTitle: _NS("Save Playlist...")];
139     [o_mi_play setTitle: _NS("Play")];
140     [o_mi_delete setTitle: _NS("Delete")];
141     [o_mi_selectall setTitle: _NS("Select All")];
142     [[o_tc_name headerCell] setStringValue:_NS("Name")];
143     [[o_tc_author headerCell] setStringValue:_NS("Author")];
144     [o_random_ckb setTitle: _NS("Random")];
145     [o_loop_ckb setTitle: _NS("Repeat All")];
146     [o_repeat_ckb setTitle: _NS("Repeat One")];
147
148
149     vlc_value_t val;
150     intf_thread_t * p_intf = [NSApp getIntf];
151     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
152                                                        FIND_ANYWHERE );
153     if( p_playlist != NULL )
154     {
155     int i_state;
156
157     var_Get( p_playlist, "random", &val );
158     i_state = val.b_bool ? NSOnState : NSOffState;
159     [o_random_ckb setState: i_state];
160
161     var_Get( p_playlist, "loop", &val );
162     i_state = val.b_bool ? NSOnState : NSOffState;
163     [o_loop_ckb setState: i_state];
164
165     var_Get( p_playlist, "repeat", &val );
166     i_state = val.b_bool ? NSOnState : NSOffState;
167     [o_repeat_ckb setState: i_state];
168
169     vlc_mutex_unlock( &p_playlist->object_lock );
170     vlc_object_release( p_playlist );
171     }
172
173
174 }
175
176 - (BOOL)tableView:(NSTableView *)o_tv 
177                   shouldEditTableColumn:(NSTableColumn *)o_tc
178                   row:(int)i_row
179 {
180     return( NO );
181 }
182
183 - (NSMenu *)menuForEvent:(NSEvent *)o_event
184 {
185     NSPoint pt;
186     vlc_bool_t b_rows;
187     vlc_bool_t b_item_sel;
188
189     pt = [o_table_view convertPoint: [o_event locationInWindow] 
190                                                  fromView: nil];
191     b_item_sel = ( [o_table_view rowAtPoint: pt] != -1 &&
192                    [o_table_view selectedRow] != -1 );
193     b_rows = [o_table_view numberOfRows] != 0;
194
195     [o_mi_play setEnabled: b_item_sel];
196     [o_mi_delete setEnabled: b_item_sel];
197     [o_mi_selectall setEnabled: b_rows];
198
199     return( o_ctx_menu );
200 }
201
202 - (IBAction)toggleWindow:(id)sender
203 {
204     if( [o_window isVisible] )
205     {
206         [o_window orderOut:sender];
207     }
208     else
209     {
210         [o_window makeKeyAndOrderFront:sender];
211     }
212 }
213
214 - (IBAction)savePlaylist:(id)sender
215 {
216     intf_thread_t * p_intf = [NSApp getIntf];
217     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
218                                                        FIND_ANYWHERE );
219     
220     NSSavePanel *o_save_panel = [NSSavePanel savePanel];
221     NSString * o_name = [NSString stringWithFormat: @"%@.m3u", _NS("Untitled")];
222     [o_save_panel setTitle: _NS("Save Playlist")];
223     [o_save_panel setPrompt: _NS("Save")];
224
225     if( [o_save_panel runModalForDirectory: nil
226             file: o_name] == NSOKButton )
227     {
228         playlist_SaveFile( p_playlist, [[o_save_panel filename] fileSystemRepresentation] );
229     }
230
231 }
232
233 - (IBAction)playItem:(id)sender
234 {
235     intf_thread_t * p_intf = [NSApp getIntf];
236     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
237                                                        FIND_ANYWHERE );
238
239     if( p_playlist != NULL )
240     {
241         playlist_Goto( p_playlist, [o_table_view selectedRow] );
242         vlc_object_release( p_playlist );
243     }
244 }
245
246 - (IBAction)deleteItems:(id)sender
247 {
248     int i, c, i_row;
249     NSMutableArray *o_to_delete;
250     NSNumber *o_number;
251
252     intf_thread_t * p_intf = [NSApp getIntf];
253     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
254                                                        FIND_ANYWHERE );
255
256     if( p_playlist == NULL )
257     {
258         return;
259     }
260     
261     o_to_delete = [NSMutableArray arrayWithArray:[[o_table_view selectedRowEnumerator] allObjects]];
262     c = (int)[o_to_delete count];
263     
264     for( i = 0; i < c; i++ ) {
265         o_number = [o_to_delete lastObject];
266         i_row = [o_number intValue];
267         
268         if( p_playlist->i_index == i_row && p_playlist->i_status )
269         {
270             playlist_Stop( p_playlist );
271         }
272         [o_to_delete removeObject: o_number];
273         [o_table_view deselectRow: i_row];
274         playlist_Delete( p_playlist, i_row );
275     }
276
277     vlc_object_release( p_playlist );
278
279     /* this is actually duplicity, because the intf.m manage also updates the view
280      * when the playlist changes. we do this on purpose, because else there is a 
281      * delay of .5 sec or so when we delete an item */
282     [self playlistUpdated];
283     [self updateRowSelection];
284 }
285
286 - (IBAction)selectAll:(id)sender
287 {
288     [o_table_view selectAll: nil];
289 }
290
291
292 - (IBAction)searchItem:(id)sender
293 {
294     int i_start;
295     int i_current;
296     id o_current_name;
297
298     intf_thread_t * p_intf = [NSApp getIntf];
299     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
300                                                FIND_ANYWHERE );
301     if ( [o_table_view selectedRow] == [o_table_view numberOfRows]-1 )
302     {
303          i_start = -1;
304     }
305     else 
306     {
307          i_start = [o_table_view selectedRow];
308     }
309
310     for ( i_current = i_start + 1 ; i_current < [o_table_view numberOfRows] ; i_current++ )
311     {
312
313         if( p_playlist == NULL )
314         {
315             o_current_name = nil;
316         }
317
318
319         else 
320         {
321             vlc_mutex_lock( &p_playlist->object_lock );
322             o_current_name = [[NSString stringWithUTF8String: 
323                 p_playlist->pp_items[i_current]->psz_name] lastPathComponent]; 
324             vlc_mutex_unlock( &p_playlist->object_lock );
325         }
326
327         if( [o_current_name rangeOfString:[o_search_keyword stringValue] options:NSCaseInsensitiveSearch ].length )
328         {
329              [o_table_view selectRow: i_current byExtendingSelection: NO];
330              [o_table_view scrollRowToVisible: i_current];
331              break;
332         }
333     [o_table_view selectRow: i_current byExtendingSelection: NO];
334     [o_table_view scrollRowToVisible: i_current];
335     }
336     vlc_object_release( p_playlist );
337 }
338
339 - (void)appendArray:(NSArray*)o_array atPos:(int)i_position enqueue:(BOOL)b_enqueue
340 {
341     int i_item;
342     intf_thread_t * p_intf = [NSApp getIntf];
343     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
344                                                        FIND_ANYWHERE );
345
346     if( p_playlist == NULL )
347     {
348         return;
349     }
350
351     for ( i_item = 0; i_item < (int)[o_array count]; i_item++ )
352     {
353         /* One item */
354         NSDictionary *o_one_item;
355         int j;
356         int i_total_options = 0;
357         int i_mode = PLAYLIST_INSERT;
358         BOOL b_rem = FALSE, b_dir = FALSE;
359         NSString *o_url, *o_name;
360         NSArray *o_options;
361         char **ppsz_options = NULL;
362     
363         /* Get the item */
364         o_one_item = [o_array objectAtIndex: i_item];
365         o_url = (NSString *)[o_one_item objectForKey: @"ITEM_URL"];
366         o_name = (NSString *)[o_one_item objectForKey: @"ITEM_NAME"];
367         o_options = (NSArray *)[o_one_item objectForKey: @"ITEM_OPTIONS"];
368         
369         /* If no name, then make a guess */
370         if( !o_name) o_name = [[NSFileManager defaultManager] displayNameAtPath: o_url];
371     
372         if( [[NSFileManager defaultManager] fileExistsAtPath:o_url isDirectory:&b_dir] && b_dir &&
373             [[NSWorkspace sharedWorkspace] getFileSystemInfoForPath: o_url isRemovable: &b_rem
374                     isWritable:NULL isUnmountable:NULL description:NULL type:NULL] && b_rem   )
375         {
376             /* All of this is to make sure CD's play when you D&D them on VLC */
377             /* Converts mountpoint to a /dev file */
378             struct statfs *buf;
379             char *psz_dev, *temp;
380             buf = (struct statfs *) malloc (sizeof(struct statfs));
381             statfs( [o_url fileSystemRepresentation], buf );
382             psz_dev = strdup(buf->f_mntfromname);
383             free( buf );
384             temp = strrchr( psz_dev , 's' );
385             psz_dev[temp - psz_dev] = '\0';
386             o_url = [NSString stringWithCString: psz_dev ];
387         }
388     
389         if (i_item == 0 && !b_enqueue)
390             i_mode |= PLAYLIST_GO;
391     
392         if( o_options && [o_options count] > 0 )
393         {
394             /* Count the input options */
395             i_total_options = [o_options count];
396     
397             /* Allocate ppsz_options */
398             for( j = 0; j < i_total_options; j++ )
399             {
400                 if( !ppsz_options )
401                     ppsz_options = (char **)malloc( sizeof(char *) * i_total_options );
402     
403                 ppsz_options[j] = strdup([[o_options objectAtIndex:j] UTF8String]);
404             }
405         }
406     
407         playlist_AddExt( p_playlist, [o_url fileSystemRepresentation], [o_name UTF8String], -1, 
408             (ppsz_options != NULL ) ? (const char **)ppsz_options : 0, i_total_options,
409             i_mode, i_position == -1 ? PLAYLIST_END : i_position + i_item);
410     
411         /* clean up */
412         for( j = 0; j < i_total_options; j++ )
413             free( ppsz_options[j] );
414         if( ppsz_options ) free( ppsz_options );
415     
416         /* Recent documents menu */
417         NSURL *o_true_url = [NSURL fileURLWithPath: o_url];
418         if( o_true_url != nil )
419         { 
420             [[NSDocumentController sharedDocumentController]
421                 noteNewRecentDocumentURL: o_true_url]; 
422         }
423     }
424
425     vlc_object_release( p_playlist );
426 }
427
428 - (void)playlistUpdated
429 {
430     [o_table_view reloadData];
431 }
432
433 - (void)updateRowSelection
434 {
435     int i_row;
436
437     intf_thread_t * p_intf = [NSApp getIntf];
438     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
439                                                        FIND_ANYWHERE );
440
441     if( p_playlist == NULL )
442     {
443         return;
444     }
445
446     i_row = p_playlist->i_index;
447     vlc_object_release( p_playlist );
448
449     [o_table_view selectRow: i_row byExtendingSelection: NO];
450     [o_table_view scrollRowToVisible: i_row];
451 }
452     
453
454 @end
455
456 @implementation VLCPlaylist (NSTableDataSource)
457
458 - (int)numberOfRowsInTableView:(NSTableView *)o_tv
459 {
460     int i_count = 0;
461     intf_thread_t * p_intf = [NSApp getIntf];
462     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
463                                                        FIND_ANYWHERE );
464
465     if( p_playlist != NULL )
466     {
467         vlc_mutex_lock( &p_playlist->object_lock );
468         i_count = p_playlist->i_size;
469         vlc_mutex_unlock( &p_playlist->object_lock );
470         vlc_object_release( p_playlist );
471     }
472     [o_status_field setStringValue: [NSString stringWithFormat:_NS("%i items in playlist"), i_count]];
473     return( i_count );
474 }
475
476 - (id)tableView:(NSTableView *)o_tv 
477                 objectValueForTableColumn:(NSTableColumn *)o_tc 
478                 row:(int)i_row
479 {
480     id o_value = nil;
481     intf_thread_t * p_intf = [NSApp getIntf];
482     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
483                                                FIND_ANYWHERE );
484
485     if( p_playlist == NULL )
486     {
487         return( nil );
488     }
489
490     if( [[o_tc identifier] isEqualToString:@"0"] )
491     {
492         o_value = [NSString stringWithFormat:@"%i", i_row + 1];
493     }
494     else if( [[o_tc identifier] isEqualToString:@"1"] )
495     {
496         vlc_mutex_lock( &p_playlist->object_lock );
497         o_value = [[NSString stringWithUTF8String: 
498             p_playlist->pp_items[i_row]->psz_name] lastPathComponent]; 
499         vlc_mutex_unlock( &p_playlist->object_lock );
500     }
501     else if( [[o_tc identifier] isEqualToString:@"2"] )
502     {
503         vlc_mutex_lock( &p_playlist->object_lock );
504         o_value = [NSString stringWithUTF8String: 
505             p_playlist->pp_items[i_row]->psz_author]; 
506         vlc_mutex_unlock( &p_playlist->object_lock );
507     }
508
509     vlc_object_release( p_playlist );
510
511     return( o_value );
512 }
513
514 - (BOOL)tableView:(NSTableView *)o_tv
515                     writeRows:(NSArray*)o_rows
516                     toPasteboard:(NSPasteboard*)o_pasteboard 
517 {
518     int i_rows = [o_rows count];
519     NSArray *o_filenames = [NSArray array];
520     
521     [o_pasteboard declareTypes:[NSArray arrayWithObject:NSFilenamesPboardType] owner:self];
522     [o_pasteboard setPropertyList:o_filenames forType:NSFilenamesPboardType];
523     if ( i_rows == 1 )
524     {
525         i_moveRow = [[o_rows objectAtIndex:0]intValue];
526         return YES;
527     }
528     return NO;
529 }
530
531 - (NSDragOperation)tableView:(NSTableView*)o_tv
532                     validateDrop:(id <NSDraggingInfo>)o_info
533                     proposedRow:(int)i_row
534                     proposedDropOperation:(NSTableViewDropOperation)o_operation 
535 {
536     if ( o_operation == NSTableViewDropAbove )
537     {
538         if ( i_moveRow >= 0 )
539         {
540             if ( i_row != i_moveRow )
541             {
542                 return NSDragOperationMove;
543             }
544             /* what if in the previous run, the row wasn't actually moved? 
545                then we can't drop new files on this location */
546             return NSDragOperationNone;
547         }
548         return NSDragOperationGeneric;
549     }
550     return NSDragOperationNone;
551 }
552
553 - (BOOL)tableView:(NSTableView*)o_tv
554                     acceptDrop:(id <NSDraggingInfo>)o_info
555                     row:(int)i_proposed_row
556                     dropOperation:(NSTableViewDropOperation)o_operation 
557 {
558     if (  i_moveRow >= 0 )
559     {
560         if (i_moveRow != -1 && i_proposed_row != -1)
561         {
562             intf_thread_t * p_intf = [NSApp getIntf];
563             playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
564                                                             FIND_ANYWHERE );
565         
566             if( p_playlist == NULL )
567             {
568                 i_moveRow = -1;
569                 return NO;
570             }
571     
572             playlist_Move( p_playlist, i_moveRow, i_proposed_row ); 
573         
574             vlc_object_release( p_playlist );
575         }
576         [self playlistUpdated];
577         i_moveRow = -1;
578         return YES;
579     }
580     else
581     {
582         NSPasteboard * o_pasteboard;
583         o_pasteboard = [o_info draggingPasteboard];
584         
585         if( [[o_pasteboard types] containsObject: NSFilenamesPboardType] )
586         {
587             int i;
588             NSArray *o_array = [NSArray array];
589             NSArray *o_values = [[o_pasteboard propertyListForType: NSFilenamesPboardType]
590                         sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
591
592             for( i = 0; i < (int)[o_values count]; i++)
593             {
594                 NSDictionary *o_dic;
595                 o_dic = [NSDictionary dictionaryWithObject:[o_values objectAtIndex:i] forKey:@"ITEM_URL"];
596                 o_array = [o_array arrayByAddingObject: o_dic];
597             }
598             [self appendArray: o_array atPos: i_proposed_row enqueue:YES];
599             return YES;
600         }
601         return NO;
602     }
603     [self updateRowSelection];
604 }
605
606 @end
607