]> git.sesse.net Git - vlc/blob - modules/gui/macosx/playlist.m
* modules/gui/macosx/playlist.?: implemented Save Playlist
[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.33 2003/09/22 03:40:05 hartman 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
33 #include "intf.h"
34 #include "playlist.h"
35 #include "controls.h"
36
37 int MacVersion102 = -1;
38
39 /*****************************************************************************
40  * VLCPlaylistView implementation 
41  *****************************************************************************/
42 @implementation VLCPlaylistView
43
44 - (void)dealloc
45 {
46     if( o_striped_row_color != nil )
47     {
48         [o_striped_row_color release];
49     }
50     [super dealloc];
51 }
52
53 - (NSMenu *)menuForEvent:(NSEvent *)o_event
54 {
55     return( [[self delegate] menuForEvent: o_event] );
56 }
57
58 - (void)keyDown:(NSEvent *)o_event
59 {
60     unichar key = 0;
61     int i, c, i_row;
62     NSMutableArray *o_to_delete;
63     NSNumber *o_number;
64     
65     playlist_t * p_playlist;
66     intf_thread_t * p_intf = [NSApp getIntf];
67
68     if( [[o_event characters] length] )
69     {
70         key = [[o_event characters] characterAtIndex: 0];
71     }
72
73     p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
74                                           FIND_ANYWHERE );
75     
76     if ( p_playlist == NULL )
77     {
78         return;
79     }
80     
81     switch( key )
82     {
83         case ' ':
84             [(VLCControls *)[[NSApp delegate] getControls] play: nil];
85             break;
86
87         case NSDeleteCharacter:
88         case NSDeleteFunctionKey:
89         case NSDeleteCharFunctionKey:
90         case NSBackspaceCharacter:
91             o_to_delete = [NSMutableArray arrayWithArray:[[self selectedRowEnumerator] allObjects]];
92             c = [o_to_delete count];
93             
94             for( i = 0; i < c; i++ ) {
95                 o_number = [o_to_delete lastObject];
96                 i_row = [o_number intValue];
97                 
98                 if( p_playlist->i_index == i_row && p_playlist->i_status )
99                 {
100                     playlist_Stop( p_playlist );
101                 }
102                 [o_to_delete removeObject: o_number];
103                 [self deselectRow: i_row];
104                 playlist_Delete( p_playlist, i_row );
105             }
106             [self reloadData];
107             break;
108             
109         default:
110             [super keyDown: o_event];
111             break;
112     }
113
114     if( p_playlist != NULL )
115     {
116         vlc_object_release( p_playlist );
117     }
118 }
119
120 - (void)highlightSelectionInClipRect:(NSRect)o_rect {
121     NSRect o_new_rect;
122     float f_height = [self rowHeight] + [self intercellSpacing].height;
123     float f_origin_y = NSMaxY( o_rect );
124     int i_row = o_rect.origin.y / f_height;
125     
126     if ( i_row % 2 == 0 )
127     {
128         i_row++;
129     }
130     
131     o_new_rect.size.width = o_rect.size.width;
132     o_new_rect.size.height = f_height;
133     o_new_rect.origin.x = o_rect.origin.x;
134     o_new_rect.origin.y = i_row * f_height;
135    
136     if( ( MacVersion102 < 0 ) && ( floor( NSAppKitVersionNumber ) > NSAppKitVersionNumber10_1 ) )
137     {
138         MacVersion102 = 102;
139     }
140  
141     if ( MacVersion102 == 102 && o_striped_row_color == nil )
142     {
143         o_striped_row_color = [[[NSColor alternateSelectedControlColor]
144                                 highlightWithLevel: 0.90] retain];
145         
146     }
147     else if ( o_striped_row_color == nil )
148     {
149         /* OSX 10.1 and before ain't that smart ;) */
150         o_striped_row_color = [[NSColor whiteColor] retain];
151     }
152
153     [o_striped_row_color set];
154     
155     while ( o_new_rect.origin.y < f_origin_y ) {
156         NSRectFill( o_new_rect );
157         o_new_rect.origin.y += f_height * 2.0;
158     }
159     [super highlightSelectionInClipRect:o_rect];
160 }
161
162 @end
163
164 /*****************************************************************************
165  * VLCPlaylist implementation 
166  *****************************************************************************/
167 @implementation VLCPlaylist
168
169 - (id)init
170 {
171     self = [super init];
172     if ( self !=nil )
173     {
174         i_moveRow = -1;
175     }
176     return self;
177 }
178
179 - (void)awakeFromNib
180 {
181     [o_table_view setTarget: self];
182     [o_table_view setDelegate: self];
183     [o_table_view setDataSource: self];
184
185     [o_table_view setDoubleAction: @selector(playItem:)];
186
187     [o_table_view registerForDraggedTypes: 
188         [NSArray arrayWithObjects: NSFilenamesPboardType, nil]];
189
190     [o_mi_save_playlist setTitle: _NS("Save Playlist...")];
191     [o_mi_play setTitle: _NS("Play")];
192     [o_mi_delete setTitle: _NS("Delete")];
193     [o_mi_selectall setTitle: _NS("Select All")];
194     
195     [o_btn_add setToolTip: _NS("Add")];
196     [o_btn_remove setToolTip: _NS("Delete")];
197 }
198
199 - (BOOL)tableView:(NSTableView *)o_tv 
200                   shouldEditTableColumn:(NSTableColumn *)o_tc
201                   row:(int)i_row
202 {
203     return( NO );
204 }
205
206 - (NSMenu *)menuForEvent:(NSEvent *)o_event
207 {
208     NSPoint pt;
209     vlc_bool_t b_rows;
210     vlc_bool_t b_item_sel;
211
212     pt = [o_table_view convertPoint: [o_event locationInWindow] 
213                                                  fromView: nil];
214     b_item_sel = ( [o_table_view rowAtPoint: pt] != -1 &&
215                    [o_table_view selectedRow] != -1 );
216     b_rows = [o_table_view numberOfRows] != 0;
217
218     [o_mi_play setEnabled: b_item_sel];
219     [o_mi_delete setEnabled: b_item_sel];
220     [o_mi_selectall setEnabled: b_rows];
221
222     return( o_ctx_menu );
223 }
224
225 - (IBAction)savePlaylist:(id)sender
226 {
227     intf_thread_t * p_intf = [NSApp getIntf];
228     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
229                                                        FIND_ANYWHERE );
230     
231     NSSavePanel *o_save_panel = [NSSavePanel savePanel];
232     NSString * o_name = [NSString stringWithFormat: @"%@.m3u", _NS("Untitled")];
233     [o_save_panel setTitle: _NS("Save Playlist")];
234     [o_save_panel setPrompt: _NS("Save")];
235
236     if( [o_save_panel runModalForDirectory: nil
237             file: o_name] == NSOKButton )
238     {
239         playlist_SaveFile( p_playlist, [[o_save_panel filename] fileSystemRepresentation] );
240     }
241
242 }
243
244 - (IBAction)playItem:(id)sender
245 {
246     intf_thread_t * p_intf = [NSApp getIntf];
247     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
248                                                        FIND_ANYWHERE );
249
250     if( p_playlist != NULL )
251     {
252         playlist_Goto( p_playlist, [o_table_view selectedRow] );
253         vlc_object_release( p_playlist );
254     }
255 }
256
257 - (IBAction)deleteItems:(id)sender
258 {
259     int i, c, i_row;
260     NSMutableArray *o_to_delete;
261     NSNumber *o_number;
262
263     intf_thread_t * p_intf = [NSApp getIntf];
264     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
265                                                        FIND_ANYWHERE );
266
267     if( p_playlist == NULL )
268     {
269         return;
270     }
271     
272     o_to_delete = [NSMutableArray arrayWithArray:[[o_table_view selectedRowEnumerator] allObjects]];
273     c = (int)[o_to_delete count];
274     
275     for( i = 0; i < c; i++ ) {
276         o_number = [o_to_delete lastObject];
277         i_row = [o_number intValue];
278         
279         if( p_playlist->i_index == i_row && p_playlist->i_status )
280         {
281             playlist_Stop( p_playlist );
282         }
283         [o_to_delete removeObject: o_number];
284         [o_table_view deselectRow: i_row];
285         playlist_Delete( p_playlist, i_row );
286     }
287
288     vlc_object_release( p_playlist );
289
290     /* this is actually duplicity, because the intf.m manage also updates the view
291      * when the playlist changes. we do this on purpose, because else there is a 
292      * delay of .5 sec or so when we delete an item */
293     [self playlistUpdated];
294     [self updateRowSelection];
295 }
296
297 - (IBAction)selectAll:(id)sender
298 {
299     [o_table_view selectAll: nil];
300 }
301
302 - (void)appendArray:(NSArray*)o_array atPos:(int)i_position enqueue:(BOOL)b_enqueue
303 {
304     int i_item;
305     intf_thread_t * p_intf = [NSApp getIntf];
306     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
307                                                        FIND_ANYWHERE );
308
309     if( p_playlist == NULL )
310     {
311         return;
312     }
313
314     for ( i_item = 0; i_item < (int)[o_array count]; i_item++ )
315     {
316         /* One item */
317         NSDictionary *o_one_item;
318         NSString *o_url;
319         NSString *o_name;
320         NSArray *o_options;
321         int j, i_total_options = 0;
322         char **ppsz_options = NULL;
323         int i_mode = PLAYLIST_INSERT;
324         
325         /* Get the item */
326         o_one_item = [o_array objectAtIndex: i_item];
327         o_url = (NSString *)[o_one_item objectForKey: @"ITEM_URL"];
328         o_name = (NSString *)[o_one_item objectForKey: @"ITEM_NAME"];
329         o_options = (NSArray *)[o_one_item objectForKey: @"ITEM_OPTIONS"];
330         
331         if( !o_name) o_name = o_url;
332         
333         if (i_item == 0 && !b_enqueue)
334             i_mode |= PLAYLIST_GO;
335
336         if( o_options && [o_options count] > 0 )
337         {
338             /* Count the input options */
339             i_total_options = [o_options count];
340     
341             /* Allocate ppsz_options */
342             for( j = 0; j < i_total_options; j++ )
343             {
344                 if( !ppsz_options )
345                     ppsz_options = (char **)malloc( sizeof(char *) * i_total_options );
346     
347                 ppsz_options[j] = strdup([[o_options objectAtIndex:j] UTF8String]);
348             }
349         }
350         
351         playlist_AddExt( p_playlist, [o_name UTF8String], [o_url fileSystemRepresentation], -1, 
352             (ppsz_options != NULL ) ? (const char **)ppsz_options : 0, i_total_options,
353             i_mode, i_position == -1 ? PLAYLIST_END : i_position + i_item );
354
355         /* clean up */
356         for( j = 0; j < i_total_options; j++ )
357             free( ppsz_options[j] );
358         if( ppsz_options ) free( ppsz_options );
359
360         NSURL *o_true_url = [NSURL fileURLWithPath: o_url];
361         if( o_true_url != nil )
362         { 
363             [[NSDocumentController sharedDocumentController]
364                 noteNewRecentDocumentURL: o_true_url]; 
365         }
366     }
367
368     vlc_object_release( p_playlist );
369 }
370
371 - (void)playlistUpdated
372 {
373     [o_table_view reloadData];
374 }
375
376 - (void)updateRowSelection
377 {
378     int i_row;
379
380     intf_thread_t * p_intf = [NSApp getIntf];
381     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
382                                                        FIND_ANYWHERE );
383
384     if( p_playlist == NULL )
385     {
386         return;
387     }
388
389     vlc_mutex_lock( &p_playlist->object_lock );    
390     i_row = p_playlist->i_index;
391     vlc_mutex_unlock( &p_playlist->object_lock );
392     vlc_object_release( p_playlist );
393
394     [o_table_view selectRow: i_row byExtendingSelection: NO];
395     [o_table_view scrollRowToVisible: i_row];
396 }
397
398 @end
399
400 @implementation VLCPlaylist (NSTableDataSource)
401
402 - (int)numberOfRowsInTableView:(NSTableView *)o_tv
403 {
404     int i_count = 0;
405     intf_thread_t * p_intf = [NSApp getIntf];
406     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
407                                                        FIND_ANYWHERE );
408
409     if( p_playlist != NULL )
410     {
411         vlc_mutex_lock( &p_playlist->object_lock );
412         i_count = p_playlist->i_size;
413         vlc_mutex_unlock( &p_playlist->object_lock );
414         vlc_object_release( p_playlist );
415     }
416
417     return( i_count );
418 }
419
420 - (id)tableView:(NSTableView *)o_tv 
421                 objectValueForTableColumn:(NSTableColumn *)o_tc 
422                 row:(int)i_row
423 {
424     id o_value = nil;
425     intf_thread_t * p_intf = [NSApp getIntf];
426     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
427                                                FIND_ANYWHERE );
428
429     if( p_playlist == NULL )
430     {
431         return( nil );
432     }
433
434     vlc_mutex_lock( &p_playlist->object_lock );
435     o_value = [[NSString stringWithUTF8String: 
436         p_playlist->pp_items[i_row]->psz_name] lastPathComponent]; 
437     vlc_mutex_unlock( &p_playlist->object_lock ); 
438
439     vlc_object_release( p_playlist );
440
441     return( o_value );
442 }
443
444 - (BOOL)tableView:(NSTableView *)o_tv
445                     writeRows:(NSArray*)o_rows
446                     toPasteboard:(NSPasteboard*)o_pasteboard 
447 {
448     int i_rows = [o_rows count];
449     NSArray *o_filenames = [NSArray array];
450     
451     [o_pasteboard declareTypes:[NSArray arrayWithObject:NSFilenamesPboardType] owner:self];
452     [o_pasteboard setPropertyList:o_filenames forType:NSFilenamesPboardType];
453     if ( i_rows == 1 )
454     {
455         i_moveRow = [[o_rows objectAtIndex:0]intValue];
456         return YES;
457     }
458     return NO;
459 }
460
461 - (NSDragOperation)tableView:(NSTableView*)o_tv
462                     validateDrop:(id <NSDraggingInfo>)o_info
463                     proposedRow:(int)i_row
464                     proposedDropOperation:(NSTableViewDropOperation)o_operation 
465 {
466     if ( o_operation == NSTableViewDropAbove )
467     {
468         if ( i_moveRow >= 0 )
469         {
470             if ( i_row != i_moveRow )
471             {
472                 return NSDragOperationMove;
473             }
474             /* what if in the previous run, the row wasn't actually moved? 
475                then we can't drop new files on this location */
476             return NSDragOperationNone;
477         }
478         return NSDragOperationGeneric;
479     }
480     return NSDragOperationNone;
481 }
482
483 - (BOOL)tableView:(NSTableView*)o_tv
484                     acceptDrop:(id <NSDraggingInfo>)o_info
485                     row:(int)i_proposed_row
486                     dropOperation:(NSTableViewDropOperation)o_operation 
487 {
488     if (  i_moveRow >= 0 )
489     {
490         if (i_moveRow != -1 && i_proposed_row != -1)
491         {
492             intf_thread_t * p_intf = [NSApp getIntf];
493             playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
494                                                             FIND_ANYWHERE );
495         
496             if( p_playlist == NULL )
497             {
498                 i_moveRow = -1;
499                 return NO;
500             }
501     
502             playlist_Move( p_playlist, i_moveRow, i_proposed_row ); 
503         
504             vlc_object_release( p_playlist );
505         }
506         [self playlistUpdated];
507         i_moveRow = -1;
508         return YES;
509     }
510     else
511     {
512         NSPasteboard * o_pasteboard;
513         o_pasteboard = [o_info draggingPasteboard];
514         
515         if( [[o_pasteboard types] containsObject: NSFilenamesPboardType] )
516         {
517             int i;
518             NSArray *o_array = [NSArray array];
519             NSArray *o_values = [[o_pasteboard propertyListForType: NSFilenamesPboardType]
520                         sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
521
522             for( i = 0; i < (int)[o_values count]; i++)
523             {
524                 NSDictionary *o_dic;
525                 o_dic = [NSDictionary dictionaryWithObject:[o_values objectAtIndex:i] forKey:@"ITEM_URL"];
526                 o_array = [o_array arrayByAddingObject: o_dic];
527             }
528             [self appendArray: o_array atPos: i_proposed_row enqueue:YES];
529
530             return( YES );
531         }
532         
533         return( NO );
534     }
535     [self updateRowSelection];
536 }
537
538 @end
539