]> git.sesse.net Git - vlc/blob - modules/gui/macosx/playlist.m
e36e3f8b269c4ccf132e73686183b2a5b5c015a4
[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.15 2003/03/17 17:10:21 hartman Exp $
6  *
7  * Authors: Jon Lech Johansen <jon-vl@nanocrew.net>
8  *          Derk-Jan Hartman <thedj@users.sourceforge.net>
9  * Thanks:  Andrew Stone for documenting the row reordering methods on the net
10  *              http://www.omnigroup.com/mailman/archive/macosx-dev/
11  *              2001-January/008195.html
12  *          Apple Computer for documenting the Alternating row colors
13  *              http://developer.apple.com/samplecode/Sample_Code/Cocoa/
14  *              MP3_Player/MyTableView.m.htm
15  *
16  * This program is free software; you can redistribute it and/or modify
17  * it under the terms of the GNU General Public License as published by
18  * the Free Software Foundation; either version 2 of the License, or
19  * (at your option) any later version.
20  * 
21  * This program is distributed in the hope that it will be useful,
22  * but WITHOUT ANY WARRANTY; without even the implied warranty of
23  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
24  * GNU General Public License for more details.
25  *
26  * You should have received a copy of the GNU General Public License
27  * along with this program; if not, write to the Free Software
28  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
29  *****************************************************************************/
30
31 /*****************************************************************************
32  * Preamble
33  *****************************************************************************/
34 #include <stdlib.h>                                      /* malloc(), free() */
35 #include <sys/param.h>                                    /* for MAXPATHLEN */
36 #include <string.h>
37
38 #include "intf.h"
39 #include "playlist.h"
40
41 // RGB values for stripe color (light blue)
42 #define STRIPE_RED   (237.0 / 255.0)
43 #define STRIPE_GREEN (243.0 / 255.0)
44 #define STRIPE_BLUE  (254.0 / 255.0)
45 static NSColor *sStripeColor = nil;
46
47 /*****************************************************************************
48  * VLCPlaylistView implementation 
49  *****************************************************************************/
50 @implementation VLCPlaylistView
51
52 - (NSMenu *)menuForEvent:(NSEvent *)o_event
53 {
54     return( [[self delegate] menuForEvent: o_event] );
55 }
56
57 - (void)keyDown:(NSEvent *)o_event
58 {
59     unichar key = 0;
60     int i_row;
61     playlist_t * p_playlist;
62     intf_thread_t * p_intf = [NSApp getIntf];
63
64     if( [[o_event characters] length] )
65     {
66         key = [[o_event characters] characterAtIndex: 0];
67     }
68
69     p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
70                                           FIND_ANYWHERE );
71     
72     if ( p_playlist == NULL )
73     {
74         return;
75     }
76     
77     switch( key )
78     {
79         case ' ':
80             vlc_mutex_lock( &p_playlist->object_lock );
81             if( p_playlist->p_input != NULL )
82             {
83                 input_SetStatus( p_playlist->p_input, INPUT_STATUS_PAUSE );
84             }
85             vlc_mutex_unlock( &p_playlist->object_lock );
86             break;
87
88         case NSDeleteCharacter:
89         case NSDeleteFunctionKey:
90         case NSDeleteCharFunctionKey:
91         case NSBackspaceCharacter:
92             while( ( i_row = [self selectedRow] ) != -1 )
93             {
94                 if( p_playlist->i_index == i_row && p_playlist->i_status )
95                 {
96                     playlist_Stop( p_playlist );
97                 }
98         
99                 playlist_Delete( p_playlist, i_row ); 
100         
101                 [self deselectRow: i_row];
102             }
103             [self reloadData];
104             break;
105             
106         default:
107             [super keyDown: o_event];
108             break;
109     }
110
111     if( p_playlist != NULL )
112     {
113         vlc_object_release( p_playlist );
114     }
115 }
116
117
118 /* This is called after the table background is filled in, but before the cell contents are drawn.
119  * We override it so we can do our own light-blue row stripes a la iTunes.
120  */
121 - (void) highlightSelectionInClipRect:(NSRect)rect {
122     [self drawStripesInRect:rect];
123     [super highlightSelectionInClipRect:rect];
124 }
125
126 /* This routine does the actual blue stripe drawing, filling in every other row of the table
127  * with a blue background so you can follow the rows easier with your eyes.
128  */
129 - (void) drawStripesInRect:(NSRect)clipRect {
130     NSRect stripeRect;
131     float fullRowHeight = [self rowHeight] + [self intercellSpacing].height;
132     float clipBottom = NSMaxY(clipRect);
133     int firstStripe = clipRect.origin.y / fullRowHeight;
134     if (firstStripe % 2 == 0)
135         firstStripe++;   // we're only interested in drawing the stripes
136                          // set up first rect
137     stripeRect.origin.x = clipRect.origin.x;
138     stripeRect.origin.y = firstStripe * fullRowHeight;
139     stripeRect.size.width = clipRect.size.width;
140     stripeRect.size.height = fullRowHeight;
141     // set the color
142     if (sStripeColor == nil)
143         sStripeColor = [[NSColor colorWithCalibratedRed:STRIPE_RED green:STRIPE_GREEN blue:STRIPE_BLUE alpha:1.0] retain];
144     [sStripeColor set];
145     // and draw the stripes
146     while (stripeRect.origin.y < clipBottom) {
147         NSRectFill(stripeRect);
148         stripeRect.origin.y += fullRowHeight * 2.0;
149     }
150 }
151
152 @end
153
154 /*****************************************************************************
155  * VLCPlaylist implementation 
156  *****************************************************************************/
157 @implementation VLCPlaylist
158
159 - (void)awakeFromNib
160 {
161     [o_table_view setTarget: self];
162     [o_table_view setDelegate: self];
163     [o_table_view setDataSource: self];
164
165     [o_table_view setDoubleAction: @selector(playItem:)];
166
167     [o_table_view registerForDraggedTypes: 
168         [NSArray arrayWithObjects: NSFilenamesPboardType, nil]];
169
170     [o_mi_play setTitle: _NS("Play")];
171     [o_mi_delete setTitle: _NS("Delete")];
172     [o_mi_selectall setTitle: _NS("Select All")];
173     
174     [o_btn_add setToolTip: _NS("Add")];
175     [o_btn_remove setToolTip: _NS("Delete")];
176 }
177
178 - (BOOL)tableView:(NSTableView *)o_tv 
179                   shouldEditTableColumn:(NSTableColumn *)o_tc
180                   row:(int)i_row
181 {
182     return( NO );
183 }
184
185 - (NSMenu *)menuForEvent:(NSEvent *)o_event
186 {
187     NSPoint pt;
188     vlc_bool_t b_rows;
189     vlc_bool_t b_item_sel;
190
191     pt = [o_table_view convertPoint: [o_event locationInWindow] 
192                                                  fromView: nil];
193     b_item_sel = ( [o_table_view rowAtPoint: pt] != -1 &&
194                    [o_table_view selectedRow] != -1 );
195     b_rows = [o_table_view numberOfRows] != 0;
196
197     [o_mi_play setEnabled: b_item_sel];
198     [o_mi_delete setEnabled: b_item_sel];
199     [o_mi_selectall setEnabled: b_rows];
200
201     return( o_ctx_menu );
202 }
203
204 - (IBAction)playItem:(id)sender
205 {
206     intf_thread_t * p_intf = [NSApp getIntf];
207     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
208                                                        FIND_ANYWHERE );
209
210     if( p_playlist == NULL )
211     {
212         return;
213     }
214
215     playlist_Goto( p_playlist, [o_table_view selectedRow] );
216
217     vlc_object_release( p_playlist );
218 }
219
220 - (IBAction)deleteItems:(id)sender
221 {
222     int i_row;
223
224     intf_thread_t * p_intf = [NSApp getIntf];
225     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
226                                                        FIND_ANYWHERE );
227
228     if( p_playlist == NULL )
229     {
230         return;
231     }
232
233     while( ( i_row = [o_table_view selectedRow] ) != -1 )
234     {
235         if( p_playlist->i_index == i_row && p_playlist->i_status )
236         {
237             playlist_Stop( p_playlist );
238         }
239
240         playlist_Delete( p_playlist, i_row ); 
241
242         [o_table_view deselectRow: i_row];
243     }
244
245     vlc_object_release( p_playlist );
246
247     /* this is actually duplicity, because the intf.m manage also updates the view
248      * when the playlist changes. we do this on purpose, because else there is a 
249      * delay of .5 sec or so when we delete an item */
250     [self playlistUpdated];
251 }
252
253 - (IBAction)selectAll:(id)sender
254 {
255     [o_table_view selectAll: nil];
256 }
257
258 - (void)appendArray:(NSArray*)o_array atPos:(int)i_pos enqueue:(BOOL)b_enqueue
259 {
260     int i_items;
261     NSString * o_value;
262     NSEnumerator * o_enum;
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     i_items = 0;
273     o_enum = [o_array objectEnumerator];
274     while( ( o_value = [o_enum nextObject] ) )
275     {
276         NSURL * o_url;
277
278         int i_mode = PLAYLIST_INSERT;
279         
280         if (i_items == 0 && !b_enqueue)
281             i_mode |= PLAYLIST_GO;
282
283         playlist_Add( p_playlist, [o_value fileSystemRepresentation],
284             i_mode, i_pos == -1 ? PLAYLIST_END : i_pos + i_items );
285
286         o_url = [NSURL fileURLWithPath: o_value];
287         if( o_url != nil )
288         { 
289             [[NSDocumentController sharedDocumentController]
290                 noteNewRecentDocumentURL: o_url]; 
291         }
292
293         i_items++;
294     }
295
296     vlc_object_release( p_playlist );
297 }
298
299 - (void)playlistUpdated
300 {
301     [o_table_view reloadData];
302 }
303
304 - (void)updateRowSelection
305 {
306     int i_row;
307
308     intf_thread_t * p_intf = [NSApp getIntf];
309     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
310                                                        FIND_ANYWHERE );
311
312     if( p_playlist == NULL )
313     {
314         return;
315     }
316
317     vlc_mutex_lock( &p_playlist->object_lock );    
318     i_row = p_playlist->i_index;
319     vlc_mutex_unlock( &p_playlist->object_lock );
320     vlc_object_release( p_playlist );
321
322     [o_table_view selectRow: i_row byExtendingSelection: NO];
323     [o_table_view scrollRowToVisible: i_row];
324 }
325
326 @end
327
328 @implementation VLCPlaylist (NSTableDataSource)
329
330 - (int)numberOfRowsInTableView:(NSTableView *)o_tv
331 {
332     int i_count = 0;
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         vlc_mutex_lock( &p_playlist->object_lock );
340         i_count = p_playlist->i_size;
341         vlc_mutex_unlock( &p_playlist->object_lock );
342         vlc_object_release( p_playlist );
343     }
344
345     return( i_count );
346 }
347
348 - (id)tableView:(NSTableView *)o_tv 
349                 objectValueForTableColumn:(NSTableColumn *)o_tc 
350                 row:(int)i_row
351 {
352     id o_value = nil;
353     intf_thread_t * p_intf = [NSApp getIntf];
354     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
355                                                FIND_ANYWHERE );
356
357     if( p_playlist == NULL )
358     {
359         return( nil );
360     }
361
362     vlc_mutex_lock( &p_playlist->object_lock );
363     o_value = [[NSString stringWithUTF8String: 
364         p_playlist->pp_items[i_row]->psz_name] lastPathComponent]; 
365     vlc_mutex_unlock( &p_playlist->object_lock ); 
366
367     vlc_object_release( p_playlist );
368
369     return( o_value );
370 }
371
372 // NEW API for Dragging in TableView:
373 // typedef enum { NSTableViewDropOn, NSTableViewDropAbove } NSTableViewDropOperation;
374 // In drag and drop, used to specify a dropOperation. For example, given a table with N rows (numbered with row 0 at the top visually), a row of N-1 and operation of NSTableViewDropOn would specify a drop on the last row. To specify a drop below the last row, one would use a row of N and NSTableViewDropAbove for the operation.
375
376 static int _moveRow = -1;
377
378 - (BOOL)tableView:(NSTableView *)tv
379                     writeRows:(NSArray*)rows
380                     toPasteboard:(NSPasteboard*)pboard 
381 // This method is called after it has been determined that a drag should begin, but before the drag has been started. To refuse the drag, return NO. To start a drag, return YES and place the drag data onto the pasteboard (data, owner, etc...). The drag image and other drag related information will be set up and provided by the table view once this call returns with YES. The rows array is the list of row numbers that will be participating in the drag.
382 {
383     int rowCount = [rows count];
384     NSArray *o_filenames = [NSArray array];
385     
386     // we should allow group selection and copy between windows: PENDING
387     [pboard declareTypes:[NSArray arrayWithObject:NSFilenamesPboardType] owner:self];
388     [pboard setPropertyList:o_filenames forType:NSFilenamesPboardType];
389     if (rowCount == 1)
390     {
391         _moveRow = [[rows objectAtIndex:0]intValue];
392         return YES;
393     }
394     return NO;
395 }
396
397 - (NSDragOperation)tableView:(NSTableView*)tv
398                     validateDrop:(id <NSDraggingInfo>)info
399                     proposedRow:(int)row
400                     proposedDropOperation:(NSTableViewDropOperation)op 
401 // This method is used by NSTableView to determine a valid drop target. Based on the mouse position, the table view will suggest a proposed drop location. This method must return a value that indicates which dragging operation the data source will perform. The data source may "re-target" a drop if desired by calling setDropRow:dropOperation: and returning something other than NSDragOperationNone. One may choose to re-target for various reasons (eg. for better visual feedback when inserting into a sorted position).
402 {
403     if ( op == NSTableViewDropAbove )
404     {
405         if ( row != _moveRow && _moveRow >= 0 )
406         {
407             return NSDragOperationMove;
408         }
409         return NSDragOperationLink;
410     }
411     return NSDragOperationNone;
412 }
413
414 - (BOOL)tableView:(NSTableView*)tv
415                     acceptDrop:(id <NSDraggingInfo>)info
416                     row:(int)i_row
417                     dropOperation:(NSTableViewDropOperation)op 
418 // This method is called when the mouse is released over an outline view that previously decided to allow a drop via the validateDrop method. The data source should incorporate the data from the dragging pasteboard at this time. 
419 {
420     if (  _moveRow >= 0 )
421     {
422         BOOL result = [self tableView:tv didDepositRow:_moveRow at:(int)i_row];
423         [self playlistUpdated];
424         _moveRow = -1;
425         return result;
426     }
427     else
428     {
429         NSArray * o_values;
430         NSPasteboard * o_pasteboard;
431         
432         o_pasteboard = [info draggingPasteboard];
433         
434         if( [[o_pasteboard types] containsObject: NSFilenamesPboardType] )
435         {
436             o_values = [o_pasteboard propertyListForType: NSFilenamesPboardType];
437         
438             [self appendArray: o_values atPos: i_row enqueue:YES];
439         
440             return( YES );
441         }
442         
443         return( NO );
444     }
445 }
446
447 -  (BOOL)tableView:(NSTableView *)tv didDepositRow:(int)i_row at:(int)i_newrow
448 {
449     if (i_row != -1 && i_newrow != -1)
450     {
451         intf_thread_t * p_intf = [NSApp getIntf];
452         playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
453                                                         FIND_ANYWHERE );
454     
455         if( p_playlist == NULL )
456         {
457             return NO;
458         }
459
460         playlist_Move( p_playlist, i_row, i_newrow ); 
461     
462         vlc_object_release( p_playlist );
463         return YES;
464     }
465     return NO;
466 }
467
468 @end
469