]> git.sesse.net Git - vlc/blob - modules/gui/macosx/playlist.m
String Review round one, Mac OS X interface.
[vlc] / modules / gui / macosx / playlist.m
1 /*****************************************************************************
2  * playlist.m: MacOS X interface module
3  *****************************************************************************
4  * Copyright (C) 2002-2004 VideoLAN
5  * $Id: playlist.m,v 1.55 2004/01/25 17:01:57 murray 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
108 @end
109
110 /*****************************************************************************
111  * VLCPlaylist implementation 
112  *****************************************************************************/
113 @implementation VLCPlaylist
114
115 - (id)init
116 {
117     self = [super init];
118     if ( self !=nil )
119     {
120         i_moveRow = -1;
121     }
122     return self;
123 }
124
125 - (void)awakeFromNib
126 {
127     [o_table_view setTarget: self];
128     [o_table_view setDelegate: self];
129     [o_table_view setDataSource: self];
130
131     [o_table_view setDoubleAction: @selector(playItem:)];
132
133     [o_table_view registerForDraggedTypes: 
134         [NSArray arrayWithObjects: NSFilenamesPboardType, nil]];
135
136     [o_window setExcludedFromWindowsMenu: TRUE];
137
138 /* We need to check whether _defaultTableHeaderSortImage exists, since it 
139 belongs to an Apple hidden private API, and then can "disapear" at any time*/
140
141     if ([[NSTableView class] respondsToSelector:@selector(_defaultTableHeaderSortImage)])
142     {
143     o_ascendingSortingImage = [[NSTableView class] _defaultTableHeaderSortImage];
144     }
145     else
146     {
147     o_ascendingSortingImage = nil;
148     }
149
150     if ([[NSTableView class] respondsToSelector:@selector(_defaultTableHeaderReverseSortImage)])
151     {
152         o_descendingSortingImage = [[NSTableView class] _defaultTableHeaderReverseSortImage];
153     }
154     else
155     {
156         o_descendingSortingImage = nil;
157     }
158
159     [self initStrings];
160 }
161
162 - (void)initStrings
163 {
164     [o_window setTitle: _NS("Playlist")];
165     [o_mi_save_playlist setTitle: _NS("Save Playlist...")];
166     [o_mi_play setTitle: _NS("Play")];
167     [o_mi_delete setTitle: _NS("Delete")];
168     [o_mi_selectall setTitle: _NS("Select All")];
169     [[o_tc_name headerCell] setStringValue:_NS("Name")];
170     [[o_tc_author headerCell] setStringValue:_NS("Author")];
171     [[o_tc_duration headerCell] setStringValue:_NS("Duration")];
172     [o_random_ckb setTitle: _NS("Random")];
173     [o_loop_ckb setTitle: _NS("Repeat All")];
174     [o_repeat_ckb setTitle: _NS("Repeat One")];
175     [o_search_button setTitle: _NS("Search")];
176     [o_btn_playlist setToolTip: _NS("Playlist")];
177 }
178
179 - (void) tableView:(NSTableView*)o_tv
180                   didClickTableColumn:(NSTableColumn *)o_tc
181 {
182     intf_thread_t * p_intf = [NSApp getIntf];
183     playlist_t *p_playlist =
184         (playlist_t *)vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
185                                        FIND_ANYWHERE );
186
187     int max = [[o_table_view tableColumns] count];
188     int i;
189
190     if( p_playlist == NULL )
191     {
192         return;
193     }
194
195     if (o_tc_sortColumn == o_tc )
196     { 
197         b_isSortDescending = !b_isSortDescending;
198     }
199     else if (o_tc == o_tc_name || o_tc == o_tc_author)
200     {
201         b_isSortDescending = VLC_FALSE;
202         [o_table_view setHighlightedTableColumn:o_tc];
203         o_tc_sortColumn = o_tc;
204         for (i=0;i<max;i++)
205         {
206             [o_table_view setIndicatorImage:nil inTableColumn:[[o_table_view tableColumns] objectAtIndex:i]];
207         }
208     }
209
210     if (o_tc_name == o_tc && !b_isSortDescending)
211     {    
212         playlist_SortTitle( p_playlist , 0 );
213         [o_table_view setIndicatorImage:o_ascendingSortingImage inTableColumn:o_tc];    
214     }
215     else if (o_tc_author == o_tc && !b_isSortDescending)
216     {
217         playlist_SortAuthor( p_playlist , 0 );
218         [o_table_view setIndicatorImage:o_ascendingSortingImage inTableColumn:o_tc];
219     }
220     else if (o_tc_name == o_tc && b_isSortDescending)
221     {    
222         playlist_SortTitle( p_playlist , 1 );
223         [o_table_view setIndicatorImage:o_descendingSortingImage inTableColumn:o_tc];
224     }
225     else if (o_tc_author == o_tc && b_isSortDescending)
226     {
227         playlist_SortAuthor( p_playlist , 1 );
228         [o_table_view setIndicatorImage:o_descendingSortingImage inTableColumn:o_tc];
229     } 
230     vlc_object_release( p_playlist );
231     [self playlistUpdated];
232 }
233
234
235 - (BOOL)tableView:(NSTableView *)o_tv 
236                   shouldEditTableColumn:(NSTableColumn *)o_tc
237                   row:(int)i_row
238 {
239     return( NO );
240 }
241
242 - (NSMenu *)menuForEvent:(NSEvent *)o_event
243 {
244     NSPoint pt;
245     vlc_bool_t b_rows;
246     vlc_bool_t b_item_sel;
247
248     pt = [o_table_view convertPoint: [o_event locationInWindow] 
249                                                  fromView: nil];
250     b_item_sel = ( [o_table_view rowAtPoint: pt] != -1 &&
251                    [o_table_view selectedRow] != -1 );
252     b_rows = [o_table_view numberOfRows] != 0;
253
254     [o_mi_play setEnabled: b_item_sel];
255     [o_mi_delete setEnabled: b_item_sel];
256     [o_mi_selectall setEnabled: b_rows];
257
258     return( o_ctx_menu );
259 }
260
261 - (IBAction)toggleWindow:(id)sender
262 {
263     if( [o_window isVisible] )
264     {
265         [o_window orderOut:sender];
266         [o_btn_playlist setState:NSOffState];
267     }
268     else
269     {
270         [o_window makeKeyAndOrderFront:sender];
271         [o_btn_playlist setState:NSOnState];
272     }
273 }
274
275 - (IBAction)savePlaylist:(id)sender
276 {
277     intf_thread_t * p_intf = [NSApp getIntf];
278     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
279                                                        FIND_ANYWHERE );
280     
281     NSSavePanel *o_save_panel = [NSSavePanel savePanel];
282     NSString * o_name = [NSString stringWithFormat: @"%@.m3u", _NS("Untitled")];
283     [o_save_panel setTitle: _NS("Save Playlist")];
284     [o_save_panel setPrompt: _NS("Save")];
285
286     if( [o_save_panel runModalForDirectory: nil
287             file: o_name] == NSOKButton )
288     {
289         playlist_Export( p_playlist, [[o_save_panel filename] fileSystemRepresentation], "m3u" );
290     }
291
292 }
293
294 - (IBAction)playItem:(id)sender
295 {
296     intf_thread_t * p_intf = [NSApp getIntf];
297     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
298                                                        FIND_ANYWHERE );
299
300     if( p_playlist != NULL )
301     {
302         playlist_Goto( p_playlist, [o_table_view selectedRow] );
303         vlc_object_release( p_playlist );
304     }
305 }
306
307 - (IBAction)deleteItems:(id)sender
308 {
309     int i, c, i_row;
310     NSMutableArray *o_to_delete;
311     NSNumber *o_number;
312
313     intf_thread_t * p_intf = [NSApp getIntf];
314     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
315                                                        FIND_ANYWHERE );
316
317     if( p_playlist == NULL )
318     {
319         return;
320     }
321     
322     o_to_delete = [NSMutableArray arrayWithArray:[[o_table_view selectedRowEnumerator] allObjects]];
323     c = (int)[o_to_delete count];
324     
325     for( i = 0; i < c; i++ ) {
326         o_number = [o_to_delete lastObject];
327         i_row = [o_number intValue];
328         
329         if( p_playlist->i_index == i_row && p_playlist->i_status )
330         {
331             playlist_Stop( p_playlist );
332         }
333         [o_to_delete removeObject: o_number];
334         [o_table_view deselectRow: i_row];
335         playlist_Delete( p_playlist, i_row );
336     }
337
338     vlc_object_release( p_playlist );
339
340     /* this is actually duplicity, because the intf.m manage also updates the view
341      * when the playlist changes. we do this on purpose, because else there is a 
342      * delay of .5 sec or so when we delete an item */
343     [self playlistUpdated];
344     [self updateRowSelection];
345 }
346
347 - (IBAction)selectAll:(id)sender
348 {
349     [o_table_view selectAll: nil];
350 }
351
352
353 - (IBAction)searchItem:(id)sender
354 {
355     int i_current = -1;
356     NSString *o_current_name;
357     NSString *o_current_author;
358
359     intf_thread_t * p_intf = [NSApp getIntf];
360     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
361                                                FIND_ANYWHERE );
362     
363     if( p_playlist == NULL )
364     {
365         return;
366     }
367     if( [o_table_view numberOfRows] < 1 )
368     {
369         return;
370     }
371
372     if( [o_table_view selectedRow] == [o_table_view numberOfRows]-1 )
373     {
374         i_current = -1;
375     }
376     else
377     {
378         i_current = [o_table_view selectedRow]; 
379     }
380
381     do
382     {
383         i_current++;
384
385         vlc_mutex_lock( &p_playlist->object_lock );
386         o_current_name = [NSString stringWithUTF8String: 
387             p_playlist->pp_items[i_current]->psz_name];
388         o_current_author = [NSString stringWithUTF8String: 
389             playlist_GetInfo(p_playlist, i_current ,_("General"),_("Author") )];
390         vlc_mutex_unlock( &p_playlist->object_lock );
391
392
393         if( [o_current_name rangeOfString:[o_search_keyword stringValue] options:NSCaseInsensitiveSearch ].length ||
394              [o_current_author rangeOfString:[o_search_keyword stringValue] options:NSCaseInsensitiveSearch ].length )
395         {
396              [o_table_view selectRow: i_current byExtendingSelection: NO];
397              [o_table_view scrollRowToVisible: i_current];
398              break;
399         }
400         if( i_current == [o_table_view numberOfRows] - 1 )
401         {
402              i_current = -1;
403         }
404     }
405     while (i_current != [o_table_view selectedRow]);
406     vlc_object_release( p_playlist );
407 }
408
409
410 - (void)appendArray:(NSArray*)o_array atPos:(int)i_position enqueue:(BOOL)b_enqueue
411 {
412     int i_item;
413     intf_thread_t * p_intf = [NSApp getIntf];
414     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
415                                                        FIND_ANYWHERE );
416
417     if( p_playlist == NULL )
418     {
419         return;
420     }
421
422     for ( i_item = 0; i_item < (int)[o_array count]; i_item++ )
423     {
424         /* One item */
425         NSDictionary *o_one_item;
426         int j, i_new_id = -1;
427         int i_mode = PLAYLIST_INSERT;
428         BOOL b_rem = FALSE, b_dir = FALSE;
429         NSString *o_uri, *o_name;
430         NSArray *o_options;
431         NSURL *o_true_file;
432     
433         /* Get the item */
434         o_one_item = [o_array objectAtIndex: i_item];
435         o_uri = (NSString *)[o_one_item objectForKey: @"ITEM_URL"];
436         o_name = (NSString *)[o_one_item objectForKey: @"ITEM_NAME"];
437         o_options = (NSArray *)[o_one_item objectForKey: @"ITEM_OPTIONS"];
438         
439         /* If no name, then make a guess */
440         if( !o_name) o_name = [[NSFileManager defaultManager] displayNameAtPath: o_uri];
441     
442         if( [[NSFileManager defaultManager] fileExistsAtPath:o_uri isDirectory:&b_dir] && b_dir &&
443             [[NSWorkspace sharedWorkspace] getFileSystemInfoForPath: o_uri isRemovable: &b_rem
444                     isWritable:NULL isUnmountable:NULL description:NULL type:NULL] && b_rem   )
445         {
446             /* All of this is to make sure CD's play when you D&D them on VLC */
447             /* Converts mountpoint to a /dev file */
448             struct statfs *buf;
449             char *psz_dev, *temp;
450             buf = (struct statfs *) malloc (sizeof(struct statfs));
451             statfs( [o_uri fileSystemRepresentation], buf );
452             psz_dev = strdup(buf->f_mntfromname);
453             free( buf );
454             temp = strrchr( psz_dev , 's' );
455             psz_dev[temp - psz_dev] = '\0';
456             o_uri = [NSString stringWithCString: psz_dev ];
457         }
458         
459         /* Add the item */
460         i_new_id = playlist_Add( p_playlist, [o_uri fileSystemRepresentation], 
461                       [o_name UTF8String], i_mode, 
462                       i_position == -1 ? PLAYLIST_END : i_position + i_item);
463         
464         /* Add the options, when there are any */
465         if( o_options )
466         {
467             for( j = 0; j < [o_options count]; j++ )
468             {
469                 playlist_AddOption( p_playlist, i_new_id,
470                  strdup( [[o_options objectAtIndex:j] UTF8String] ) );
471             }
472         }
473         
474         if( i_item == 0 && !b_enqueue )
475         {
476             playlist_Goto( p_playlist, playlist_GetPositionById( p_playlist, i_new_id ) );
477             playlist_Play( p_playlist );
478         }
479     
480         /* Recent documents menu */
481         o_true_file = [NSURL fileURLWithPath: o_uri];
482         if( o_true_file != nil )
483         { 
484             [[NSDocumentController sharedDocumentController]
485                 noteNewRecentDocumentURL: o_true_file]; 
486         }
487     }
488
489     vlc_object_release( p_playlist );
490 }
491
492 - (void)playlistUpdated
493 {
494     vlc_value_t val;
495     intf_thread_t * p_intf = [NSApp getIntf];
496     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
497                                                        FIND_ANYWHERE );
498     if( p_playlist != NULL )
499     {
500         var_Get( p_playlist, "random", &val );
501         [o_random_ckb setState: val.b_bool];
502
503         var_Get( p_playlist, "loop", &val );
504         [o_loop_ckb setState: val.b_bool];
505
506         var_Get( p_playlist, "repeat", &val );
507         [o_repeat_ckb setState: val.b_bool];
508
509         vlc_object_release( p_playlist );
510     }
511     [o_table_view reloadData];
512 }
513
514 - (void)updateRowSelection
515 {
516     int i_row;
517
518     intf_thread_t * p_intf = [NSApp getIntf];
519     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
520                                                        FIND_ANYWHERE );
521
522     if( p_playlist == NULL )
523     {
524         return;
525     }
526
527     i_row = p_playlist->i_index;
528     vlc_object_release( p_playlist );
529
530     [o_table_view selectRow: i_row byExtendingSelection: NO];
531     [o_table_view scrollRowToVisible: i_row];
532 }
533
534
535 @end
536
537 @implementation VLCPlaylist (NSTableDataSource)
538
539 - (int)numberOfRowsInTableView:(NSTableView *)o_tv
540 {
541     int i_count = 0;
542     intf_thread_t * p_intf = [NSApp getIntf];
543     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
544                                                        FIND_ANYWHERE );
545
546     if( p_playlist != NULL )
547     {
548         vlc_mutex_lock( &p_playlist->object_lock );
549         i_count = p_playlist->i_size;
550         vlc_mutex_unlock( &p_playlist->object_lock );
551         vlc_object_release( p_playlist );
552     }
553     [o_status_field setStringValue: [NSString stringWithFormat:_NS("%i items in playlist"), i_count]];
554     return( i_count );
555 }
556
557 - (id)tableView:(NSTableView *)o_tv 
558                 objectValueForTableColumn:(NSTableColumn *)o_tc 
559                 row:(int)i_row
560 {
561     id o_value = nil;
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         return( nil );
569     }
570
571     if( [[o_tc identifier] isEqualToString:@"0"] )
572     {
573         o_value = [NSString stringWithFormat:@"%i", i_row + 1];
574     }
575     else if( [[o_tc identifier] isEqualToString:@"1"] )
576     {
577         vlc_mutex_lock( &p_playlist->object_lock );
578         o_value = [NSString stringWithUTF8String: 
579             p_playlist->pp_items[i_row]->psz_name];
580         vlc_mutex_unlock( &p_playlist->object_lock );
581     }
582     else if( [[o_tc identifier] isEqualToString:@"2"] )
583     {
584         vlc_mutex_lock( &p_playlist->object_lock );
585         o_value = [NSString stringWithUTF8String: 
586             playlist_GetInfo(p_playlist, i_row ,_("General"),_("Author") )];
587         vlc_mutex_unlock( &p_playlist->object_lock );
588     }
589     else if( [[o_tc identifier] isEqualToString:@"3"] )
590     {
591         char psz_duration[MSTRTIME_MAX_SIZE];
592         mtime_t dur = p_playlist->pp_items[i_row]->i_duration;
593         if( dur != -1 )
594         {
595             secstotimestr( psz_duration, dur/1000000 );
596             o_value = [NSString stringWithUTF8String: psz_duration];
597         }
598         else
599         {
600             o_value = @"-:--:--";
601         }
602     }
603
604     vlc_object_release( p_playlist );
605
606     return( o_value );
607 }
608
609 - (BOOL)tableView:(NSTableView *)o_tv
610                     writeRows:(NSArray*)o_rows
611                     toPasteboard:(NSPasteboard*)o_pasteboard 
612 {
613     int i_rows = [o_rows count];
614     NSArray *o_filenames = [NSArray array];
615     
616     [o_pasteboard declareTypes:[NSArray arrayWithObject:NSFilenamesPboardType] owner:self];
617     [o_pasteboard setPropertyList:o_filenames forType:NSFilenamesPboardType];
618     if ( i_rows == 1 )
619     {
620         i_moveRow = [[o_rows objectAtIndex:0]intValue];
621         return YES;
622     }
623     return NO;
624 }
625
626 - (NSDragOperation)tableView:(NSTableView*)o_tv
627                     validateDrop:(id <NSDraggingInfo>)o_info
628                     proposedRow:(int)i_row
629                     proposedDropOperation:(NSTableViewDropOperation)o_operation 
630 {
631     if ( o_operation == NSTableViewDropAbove )
632     {
633         if ( i_moveRow >= 0 )
634         {
635             if ( i_row != i_moveRow )
636             {
637                 return NSDragOperationMove;
638             }
639             /* what if in the previous run, the row wasn't actually moved? 
640                then we can't drop new files on this location */
641             return NSDragOperationNone;
642         }
643         return NSDragOperationGeneric;
644     }
645     return NSDragOperationNone;
646 }
647
648 - (BOOL)tableView:(NSTableView*)o_tv
649                     acceptDrop:(id <NSDraggingInfo>)o_info
650                     row:(int)i_proposed_row
651                     dropOperation:(NSTableViewDropOperation)o_operation 
652 {
653     if (  i_moveRow >= 0 )
654     {
655         if (i_moveRow != -1 && i_proposed_row != -1)
656         {
657             intf_thread_t * p_intf = [NSApp getIntf];
658             playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
659                                                             FIND_ANYWHERE );
660         
661             if( p_playlist == NULL )
662             {
663                 i_moveRow = -1;
664                 return NO;
665             }
666     
667             playlist_Move( p_playlist, i_moveRow, i_proposed_row ); 
668         
669             vlc_object_release( p_playlist );
670         }
671         [self playlistUpdated];
672         i_moveRow = -1;
673         return YES;
674     }
675     else
676     {
677         NSPasteboard * o_pasteboard;
678         o_pasteboard = [o_info draggingPasteboard];
679         
680         if( [[o_pasteboard types] containsObject: NSFilenamesPboardType] )
681         {
682             int i;
683             NSArray *o_array = [NSArray array];
684             NSArray *o_values = [[o_pasteboard propertyListForType: NSFilenamesPboardType]
685                         sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
686
687             for( i = 0; i < (int)[o_values count]; i++)
688             {
689                 NSDictionary *o_dic;
690                 o_dic = [NSDictionary dictionaryWithObject:[o_values objectAtIndex:i] forKey:@"ITEM_URL"];
691                 o_array = [o_array arrayByAddingObject: o_dic];
692             }
693             [self appendArray: o_array atPos: i_proposed_row enqueue:YES];
694             return YES;
695         }
696         return NO;
697     }
698     [self updateRowSelection];
699 }
700
701 /* Delegate method of NSWindow */
702 - (void)windowWillClose:(NSNotification *)aNotification
703 {
704     [o_btn_playlist setState: NSOffState];
705 }
706
707 @end
708