]> git.sesse.net Git - vlc/blob - modules/gui/macosx/playlist.m
* Implements sorting by licking on table column headers in the playlist
[vlc] / modules / gui / macosx / playlist.m
1 /*****************************************************************************
2  * playlist.m: MacOS X interface module
3  *****************************************************************************
4  * Copyright (C) 2002-2004 VideoLAN
5  * $Id$
6  *
7  * Authors: Jon Lech Johansen <jon-vl@nanocrew.net>
8  *          Derk-Jan Hartman <hartman at videolan dot org>
9  *          Benjamin Pracht <bigben at videolab dot org>
10  *
11  * This program is free software; you can redistribute it and/or modify
12  * it under the terms of the GNU General Public License as published by
13  * the Free Software Foundation; either version 2 of the License, or
14  * (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  * GNU General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with this program; if not, write to the Free Software
23  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
24  *****************************************************************************/
25
26 /* TODO
27  * add 'icons' for different types of nodes? (http://www.cocoadev.com/index.pl?IconAndTextInTableCell)
28  * create a new 'playlist toggle' that hides the playlist and in effect give you the old controller
29  * create a new search field build with pictures from the 'regular' search field, so it can be emulated on 10.2
30  * create toggle buttons for the shuffle, repeat one, repeat all functions.
31  * implement drag and drop and item reordering.
32  * reimplement enable/disable item
33  * create a new 'tool' button (see the gear button in the Finder window) for 'actions'
34    (adding service discovery, other views, new node/playlist, save node/playlist) stuff like that
35  */
36
37
38
39 /*****************************************************************************
40  * Preamble
41  *****************************************************************************/
42 #include <stdlib.h>                                      /* malloc(), free() */
43 #include <sys/param.h>                                    /* for MAXPATHLEN */
44 #include <string.h>
45 #include <math.h>
46 #include <sys/mount.h>
47 #include <vlc_keys.h>
48
49 #include "intf.h"
50 #include "playlist.h"
51 #include "controls.h"
52 #include "osd.h"
53
54 /*****************************************************************************
55  * VLCPlaylistView implementation 
56  *****************************************************************************/
57 @implementation VLCPlaylistView
58
59 - (NSMenu *)menuForEvent:(NSEvent *)o_event
60 {
61     return( [[self delegate] menuForEvent: o_event] );
62 }
63
64 - (void)keyDown:(NSEvent *)o_event
65 {
66     unichar key = 0;
67
68     if( [[o_event characters] length] )
69     {
70         key = [[o_event characters] characterAtIndex: 0];
71     }
72
73     switch( key )
74     {
75         case NSDeleteCharacter:
76         case NSDeleteFunctionKey:
77         case NSDeleteCharFunctionKey:
78         case NSBackspaceCharacter:
79             [[self delegate] deleteItem:self];
80             break;
81
82         default:
83             [super keyDown: o_event];
84             break;
85     }
86 }
87
88 @end
89
90 /*****************************************************************************
91  * VLCPlaylist implementation 
92  *****************************************************************************/
93 @implementation VLCPlaylist
94
95 - (id)init
96 {
97     self = [super init];
98     if ( self !=nil )
99     {
100         //i_moveRow = -1;
101     }
102     return self;
103 }
104
105 - (void)awakeFromNib
106 {
107     [o_outline_view setTarget: self];
108     [o_outline_view setDelegate: self];
109     [o_outline_view setDataSource: self];
110
111     [o_outline_view setDoubleAction: @selector(playItem:)];
112
113     [o_outline_view registerForDraggedTypes: 
114         [NSArray arrayWithObjects: NSFilenamesPboardType, nil]];
115     [o_outline_view setIntercellSpacing: NSMakeSize (0.0, 1.0)];
116
117 /* We need to check whether _defaultTableHeaderSortImage exists, since it 
118 belongs to an Apple hidden private API, and then can "disapear" at any time*/
119
120     if( [[NSOutlineView class] respondsToSelector:@selector(_defaultTableHeaderSortImage)] )
121     {
122         o_ascendingSortingImage = [[NSOutlineView class] _defaultTableHeaderSortImage];
123     }
124     else
125     {
126         o_ascendingSortingImage = nil;
127     }
128
129     if( [[NSOutlineView class] respondsToSelector:@selector(_defaultTableHeaderReverseSortImage)] )
130     {
131         o_descendingSortingImage = [[NSOutlineView class] _defaultTableHeaderReverseSortImage];
132     }
133     else
134     {
135         o_descendingSortingImage = nil;
136     }
137
138     o_outline_dict = [[NSMutableDictionary alloc] init];
139     o_tc_sortColumn = nil;
140
141     [self initStrings];
142     //[self playlistUpdated];
143 }
144
145 - (void)initStrings
146 {
147     [o_mi_save_playlist setTitle: _NS("Save Playlist...")];
148     [o_mi_play setTitle: _NS("Play")];
149     [o_mi_delete setTitle: _NS("Delete")];
150     [o_mi_selectall setTitle: _NS("Select All")];
151     [o_mi_info setTitle: _NS("Properties")];
152     [o_mi_sort_name setTitle: _NS("Sort Node by Name")];
153     [o_mi_sort_author setTitle: _NS("Sort Node by Author")];
154     [[o_tc_name headerCell] setStringValue:_NS("Name")];
155     [[o_tc_author headerCell] setStringValue:_NS("Author")];
156     [[o_tc_duration headerCell] setStringValue:_NS("Duration")];
157     [o_status_field setStringValue: [NSString stringWithFormat:
158                         _NS("0 items in playlist")]];
159
160     [o_random_ckb setTitle: _NS("Random")];
161 #if 0
162     [o_search_button setTitle: _NS("Search")];
163 #endif
164     [o_btn_playlist setToolTip: _NS("Playlist")];
165     [[o_loop_popup itemAtIndex:0] setTitle: _NS("Standard Play")];
166     [[o_loop_popup itemAtIndex:1] setTitle: _NS("Repeat One")];
167     [[o_loop_popup itemAtIndex:2] setTitle: _NS("Repeat All")];
168 }
169
170 - (void)playlistUpdated
171 {
172     unsigned int i;
173
174     /* Clear indications of any existing column sorting*/
175     for( i = 0 ; i < [[o_outline_view tableColumns] count] ; i++ )
176     {
177         [o_outline_view setIndicatorImage:nil inTableColumn:
178                             [[o_outline_view tableColumns] objectAtIndex:i]];
179     }
180
181     [o_outline_view setHighlightedTableColumn:nil];
182     o_tc_sortColumn = nil;
183     [o_outline_dict removeAllObjects];
184     [o_outline_view reloadData];
185 }
186
187 - (bool)isItem:(playlist_item_t *)p_item inNode:(playlist_item_t *)p_node
188 {
189     playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
190                                           FIND_ANYWHERE );
191     playlist_item_t * p_temp_item = p_item;
192
193     if ( p_playlist == NULL )
194     {
195         return NO;
196     }
197
198     while ( p_temp_item->i_parents > 0 )
199     {
200         int i;
201         for (i = 0; i < p_temp_item->i_parents ; i++)
202         {
203             if (p_temp_item->pp_parents[i]->i_view == VIEW_SIMPLE)
204             {
205                 if (p_temp_item->pp_parents[i]->p_parent == p_node)
206                 {
207                     vlc_object_release(p_playlist);
208                     return YES;
209                 }
210                 else
211                 {
212                     p_temp_item = p_temp_item->pp_parents[i]->p_parent;
213                     break;
214                 }
215             }
216         }
217     }
218
219     vlc_object_release(p_playlist);
220     return NO;
221 }
222
223
224 - (IBAction)playItem:(id)sender
225 {
226     intf_thread_t * p_intf = VLCIntf;
227     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
228                                                        FIND_ANYWHERE );
229
230     if( p_playlist != NULL )
231     {
232         playlist_item_t *p_item;
233         playlist_item_t *p_node = NULL;
234         int i;
235
236         p_item = [[o_outline_view itemAtRow:[o_outline_view selectedRow]] pointerValue];
237
238         if( p_item )
239         {
240             if (p_item->i_children == -1)
241             {
242                 for (i = 0 ; i < p_item->i_parents ; i++)
243                 {
244                     if (p_item->pp_parents[i]->i_view == VIEW_SIMPLE)
245                     {
246                         p_node = p_item->pp_parents[i]->p_parent;
247                     }
248                 }
249             }
250             else
251             {
252                 p_node = p_item;
253                 if (p_node->pp_children[0]->i_children == -1 &&
254                     p_node->i_children > 0)
255                 {
256                     p_item = p_node->pp_children[0];
257                 }
258                 else
259                 {
260                     p_item = NULL;
261                 }
262             }
263
264 //        p_view = playlist_ViewFind( p_playlist, VIEW_SIMPLE );
265
266
267             playlist_Control( p_playlist, PLAYLIST_VIEWPLAY, VIEW_SIMPLE, p_node, p_item );
268 //            playlist_Control( p_playlist, PLAYLIST_VIEWPLAY, VIEW_SIMPLE, p_view ? p_view->p_root : NULL, p_item );
269         }
270         vlc_object_release( p_playlist );
271     }
272 }
273
274 - (IBAction)selectAll:(id)sender
275 {
276     [o_outline_view selectAll: nil];
277 }
278
279 - (IBAction)deleteItem:(id)sender
280 {
281     int i, c, i_row;
282     NSMutableArray *o_to_delete;
283     NSNumber *o_number;
284
285     playlist_t * p_playlist;
286     intf_thread_t * p_intf = VLCIntf;
287
288     p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
289                                           FIND_ANYWHERE );
290
291     if ( p_playlist == NULL )
292     {
293         return;
294     }
295     o_to_delete = [NSMutableArray arrayWithArray:[[o_outline_view selectedRowEnumerator] allObjects]];
296     c = [o_to_delete count];
297
298     for( i = 0; i < c; i++ ) {
299         playlist_item_t * p_item;
300         o_number = [o_to_delete lastObject];
301         i_row = [o_number intValue];
302
303         [o_to_delete removeObject: o_number];
304         [o_outline_view deselectRow: i_row];
305         p_item = (playlist_item_t *)[[o_outline_view itemAtRow: i_row]pointerValue];
306         if (p_item->i_children > -1)
307         {
308             if (p_playlist->status.i_status)
309             {
310                 if ([self isItem:p_playlist->status.p_item inNode: p_item]
311                                     == YES && p_playlist->status.i_status)
312                 {
313                     playlist_Stop( p_playlist );
314                 }
315             }
316             playlist_NodeDelete( p_playlist, p_item, VLC_TRUE);
317         }
318         else
319         {
320             if( p_playlist->status.p_item == [[o_outline_view itemAtRow: i_row]
321                         pointerValue] && p_playlist->status.i_status )
322             {
323                 playlist_Stop( p_playlist );
324             }
325             playlist_LockDelete( p_playlist, p_item->input.i_id );
326         }
327         [self playlistUpdated];
328     }
329 }
330
331 - (IBAction)sortNodeByName:(id)sender
332 {
333     [self sortNode: SORT_TITLE];
334 }
335
336 - (IBAction)sortNodeByAuthor:(id)sender
337 {
338     [self sortNode: SORT_AUTHOR];
339 }
340
341 - (void)sortNode:(int)i_mode
342 {
343     playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
344                                           FIND_ANYWHERE );
345     playlist_item_t * p_item;
346
347     if (p_playlist == NULL)
348     {
349         return;
350     }
351
352     if ([o_outline_view selectedRow] > -1)
353     {
354         p_item = [[o_outline_view itemAtRow: [o_outline_view selectedRow]]
355                                                                 pointerValue];
356     }
357     else
358     /*If no item is selected, sort the whole playlist*/
359     {
360         playlist_view_t * p_view = playlist_ViewFind( p_playlist, VIEW_SIMPLE );
361         p_item = p_view->p_root;
362     }
363
364     if (p_item->i_children > -1)
365     {
366         vlc_mutex_lock(&p_playlist->object_lock );
367         playlist_RecursiveNodeSort( p_playlist, p_item, i_mode, ORDER_NORMAL );
368         vlc_mutex_unlock(&p_playlist->object_lock );
369     }
370     else
371     {
372         int i;
373
374         for (i = 0 ; i < p_item->i_parents ; i++)
375         {
376             if (p_item->pp_parents[i]->i_view == VIEW_SIMPLE)
377             {
378                 vlc_mutex_lock(&p_playlist->object_lock );
379                 playlist_RecursiveNodeSort( p_playlist,
380                         p_item->pp_parents[i]->p_parent, i_mode, ORDER_NORMAL );
381                 vlc_mutex_unlock(&p_playlist->object_lock );
382                 break;
383             }
384         }
385     }
386     vlc_object_release(p_playlist);
387     [self playlistUpdated];
388 }
389
390 - (void)appendArray:(NSArray*)o_array atPos:(int)i_position enqueue:(BOOL)b_enqueue
391 {
392     int i_item;
393     intf_thread_t * p_intf = VLCIntf;
394     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
395                                                        FIND_ANYWHERE );
396
397     if( p_playlist == NULL )
398     {
399         return;
400     }
401
402     for ( i_item = 0; i_item < (int)[o_array count]; i_item++ )
403     {
404         /* One item */
405         NSDictionary *o_one_item;
406         int j, i_total_options = 0, i_new_id = -1;
407         int i_mode = PLAYLIST_INSERT;
408         BOOL b_rem = FALSE, b_dir = FALSE;
409         NSString *o_uri, *o_name;
410         NSArray *o_options;
411         NSURL *o_true_file;
412         char **ppsz_options = NULL;
413
414         /* Get the item */
415         o_one_item = [o_array objectAtIndex: i_item];
416         o_uri = (NSString *)[o_one_item objectForKey: @"ITEM_URL"];
417         o_name = (NSString *)[o_one_item objectForKey: @"ITEM_NAME"];
418         o_options = (NSArray *)[o_one_item objectForKey: @"ITEM_OPTIONS"];
419
420         /* If no name, then make a guess */
421         if( !o_name) o_name = [[NSFileManager defaultManager] displayNameAtPath: o_uri];
422
423         if( [[NSFileManager defaultManager] fileExistsAtPath:o_uri isDirectory:&b_dir] && b_dir &&
424             [[NSWorkspace sharedWorkspace] getFileSystemInfoForPath: o_uri isRemovable: &b_rem
425                     isWritable:NULL isUnmountable:NULL description:NULL type:NULL] && b_rem   )
426         {
427             /* All of this is to make sure CD's play when you D&D them on VLC */
428             /* Converts mountpoint to a /dev file */
429             struct statfs *buf;
430             char *psz_dev;
431             buf = (struct statfs *) malloc (sizeof(struct statfs));
432             statfs( [o_uri fileSystemRepresentation], buf );
433             psz_dev = strdup(buf->f_mntfromname);
434             o_uri = [NSString stringWithCString: psz_dev ];
435         }
436
437         if( o_options && [o_options count] > 0 )
438         {
439             /* Count the input options */
440             i_total_options = [o_options count];
441
442             /* Allocate ppsz_options */
443             for( j = 0; j < i_total_options; j++ )
444             {
445                 if( !ppsz_options )
446                     ppsz_options = (char **)malloc( sizeof(char *) * i_total_options );
447
448                 ppsz_options[j] = strdup([[o_options objectAtIndex:j] UTF8String]);
449             }
450         }
451
452         /* Add the item */
453         i_new_id = playlist_AddExt( p_playlist, [o_uri fileSystemRepresentation],
454                       [o_name UTF8String], i_mode,
455                       i_position == -1 ? PLAYLIST_END : i_position + i_item,
456                       0, (ppsz_options != NULL ) ? (const char **)ppsz_options : 0, i_total_options );
457
458         /* clean up
459         for( j = 0; j < i_total_options; j++ )
460             free( ppsz_options[j] );
461         if( ppsz_options ) free( ppsz_options ); */
462
463         /* Recent documents menu */
464         o_true_file = [NSURL fileURLWithPath: o_uri];
465         if( o_true_file != nil )
466         {
467             [[NSDocumentController sharedDocumentController]
468                 noteNewRecentDocumentURL: o_true_file];
469         }
470
471         if( i_item == 0 && !b_enqueue )
472         {
473             playlist_Goto( p_playlist, playlist_GetPositionById( p_playlist, i_new_id ) );
474             playlist_Play( p_playlist );
475         }
476     }
477
478     vlc_object_release( p_playlist );
479 }
480
481 - (IBAction)handlePopUp:(id)sender
482
483 {
484              intf_thread_t * p_intf = VLCIntf;
485              vlc_value_t val1,val2;
486              playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
487                                                         FIND_ANYWHERE );
488              if( p_playlist == NULL )
489              {
490                  return;
491              }
492
493     switch ([o_loop_popup indexOfSelectedItem])
494     {
495         case 1:
496
497              val1.b_bool = 0;
498              var_Set( p_playlist, "loop", val1 );
499              val1.b_bool = 1;
500              var_Set( p_playlist, "repeat", val1 );
501              vout_OSDMessage( p_intf, DEFAULT_CHAN, _( "Repeat One" ) );
502         break;
503
504         case 2:
505              val1.b_bool = 0;
506              var_Set( p_playlist, "repeat", val1 );
507              val1.b_bool = 1;
508              var_Set( p_playlist, "loop", val1 );
509              vout_OSDMessage( p_intf, DEFAULT_CHAN, _( "Repeat All" ) );
510         break;
511
512         default:
513              var_Get( p_playlist, "repeat", &val1 );
514              var_Get( p_playlist, "loop", &val2 );
515              if (val1.b_bool || val2.b_bool)
516              {
517                   val1.b_bool = 0;
518                   var_Set( p_playlist, "repeat", val1 );
519                   var_Set( p_playlist, "loop", val1 );
520                   vout_OSDMessage( p_intf, DEFAULT_CHAN, _( "Repeat Off" ) );
521              }
522          break;
523      }
524      vlc_object_release( p_playlist );
525      [self playlistUpdated];
526 }
527
528 - (NSMutableArray *)subSearchItem:(playlist_item_t *)p_item
529 {
530     playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
531                                                        FIND_ANYWHERE );
532     playlist_item_t * p_selected_item;
533     int i_current, i_selected_row;
534
535     if (!p_playlist)
536         return NULL;
537
538     i_selected_row = [o_outline_view selectedRow];
539     if (i_selected_row < 0)
540         i_selected_row = 0;
541
542     p_selected_item = (playlist_item_t *)[[o_outline_view itemAtRow:
543                                             i_selected_row] pointerValue];
544
545     for (i_current = 0; i_current < p_item->i_children ; i_current++)
546     {
547         char * psz_temp;
548         NSString * o_current_name, * o_current_author;
549
550         vlc_mutex_lock( &p_playlist->object_lock );
551         o_current_name = [NSString stringWithUTF8String:
552             p_item->pp_children[i_current]->input.psz_name];
553         psz_temp = playlist_ItemGetInfo(p_item ,_("Meta-information"),_("Author") );
554         o_current_author = [NSString stringWithUTF8String: psz_temp];
555         free( psz_temp);
556         vlc_mutex_unlock( &p_playlist->object_lock );
557
558         if (p_selected_item == p_item->pp_children[i_current] &&
559                     b_selected_item_met == NO)
560         {
561             b_selected_item_met = YES;
562         }
563         else if (p_selected_item == p_item->pp_children[i_current] &&
564                     b_selected_item_met == YES)
565         {
566             vlc_object_release(p_playlist);
567             return NULL;
568         }
569         else if (b_selected_item_met == YES &&
570                     ([o_current_name rangeOfString:[o_search_field
571                         stringValue] options:NSCaseInsensitiveSearch ].length ||
572                     [o_current_author rangeOfString:[o_search_field
573                         stringValue] options:NSCaseInsensitiveSearch ].length))
574         {
575             vlc_object_release(p_playlist);
576             /*Adds the parent items in the result array as well, so that we can
577             expand the tree*/
578             return [NSMutableArray arrayWithObject: [NSValue
579                             valueWithPointer: p_item->pp_children[i_current]]];
580         }
581         if (p_item->pp_children[i_current]->i_children > 0)
582         {
583             id o_result = [self subSearchItem:
584                                             p_item->pp_children[i_current]];
585             if (o_result != NULL)
586             {
587                 vlc_object_release(p_playlist);
588                 [o_result insertObject: [NSValue valueWithPointer:
589                                 p_item->pp_children[i_current]] atIndex:0];
590                 return o_result;
591             }
592         }
593     }
594     vlc_object_release(p_playlist);
595     return NULL;
596 }
597
598 - (IBAction)searchItem:(id)sender
599 {
600     playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
601                                                        FIND_ANYWHERE );
602     playlist_view_t * p_view;
603     id o_result;
604
605     unsigned int i;
606     int i_row = -1;
607
608     b_selected_item_met = NO;
609
610     if( p_playlist == NULL )
611         return;
612
613     p_view = playlist_ViewFind( p_playlist, VIEW_SIMPLE );
614
615     if (p_view)
616     {
617         /*First, only search after the selected item:*
618          *(b_selected_item_met = NO)                 */
619         o_result = [self subSearchItem:p_view->p_root];
620         if (o_result == NULL)
621         {
622             /* If the first search failed, search again from the beginning */
623             o_result = [self subSearchItem:p_view->p_root];
624         }
625         if (o_result != NULL)
626         {
627             for (i = 0 ; i < [o_result count] - 1 ; i++)
628             {
629                 [o_outline_view expandItem: [o_outline_dict objectForKey:
630                             [NSString stringWithFormat: @"%p",
631                             [[o_result objectAtIndex: i] pointerValue]]]];
632             }
633             i_row = [o_outline_view rowForItem: [o_outline_dict objectForKey:
634                             [NSString stringWithFormat: @"%p",
635                             [[o_result objectAtIndex: [o_result count] - 1 ]
636                             pointerValue]]]];
637         }
638         if (i_row > -1)
639         {
640             [o_outline_view selectRow:i_row byExtendingSelection: NO];
641             [o_outline_view scrollRowToVisible: i_row];
642         }
643     }
644     vlc_object_release(p_playlist);
645
646 }
647
648 - (NSMenu *)menuForEvent:(NSEvent *)o_event
649 {
650     NSPoint pt;
651     vlc_bool_t b_rows;
652     vlc_bool_t b_item_sel;
653
654     pt = [o_outline_view convertPoint: [o_event locationInWindow]
655                                                  fromView: nil];
656     b_item_sel = ( [o_outline_view rowAtPoint: pt] != -1 &&
657                    [o_outline_view selectedRow] != -1 );
658     b_rows = [o_outline_view numberOfRows] != 0;
659
660     [o_mi_play setEnabled: b_item_sel];
661     [o_mi_delete setEnabled: b_item_sel];
662     [o_mi_selectall setEnabled: b_rows];
663     [o_mi_info setEnabled: b_item_sel];
664
665     return( o_ctx_menu );
666 }
667
668 - (playlist_item_t *)selectedPlaylistItem
669 {
670     return [[o_outline_view itemAtRow: [o_outline_view selectedRow]]
671                                                                 pointerValue];
672 }
673
674 - (void) outlineView:(NSTableView*)o_tv
675                   didClickTableColumn:(NSTableColumn *)o_tc
676 {
677     intf_thread_t * p_intf = VLCIntf;
678     playlist_t *p_playlist =
679         (playlist_t *)vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
680                                        FIND_ANYWHERE );
681     playlist_view_t * p_view  = playlist_ViewFind( p_playlist, VIEW_SIMPLE );
682     int i_mode = 0, i_type;
683
684     if( p_playlist == NULL )
685     {
686         return;
687     }
688
689     /* Check whether the selected table column header corresponds to a
690        sortable table column*/
691     if ( !(o_tc == o_tc_name || o_tc == o_tc_author))
692     {
693         return;
694     }
695
696     if( o_tc_sortColumn == o_tc )
697     {
698         b_isSortDescending = !b_isSortDescending;
699     }
700     else
701     {
702         b_isSortDescending = VLC_FALSE;
703     }
704
705     if (o_tc == o_tc_name)
706     {
707         i_mode = SORT_TITLE;
708     }
709     else if (o_tc == o_tc_author)
710     {
711         i_mode = SORT_AUTHOR;
712     }
713
714     if (b_isSortDescending)
715     {
716         i_type = ORDER_REVERSE;
717     }
718     else
719     {
720         i_type = ORDER_NORMAL;
721     }
722
723     vlc_mutex_lock(&p_playlist->object_lock );
724     playlist_RecursiveNodeSort(p_playlist, p_view->p_root, i_mode, i_type);
725     vlc_mutex_unlock(&p_playlist->object_lock );
726
727     vlc_object_release( p_playlist );
728     [self playlistUpdated];
729
730     o_tc_sortColumn = o_tc;
731     [o_outline_view setHighlightedTableColumn:o_tc];
732
733     if (b_isSortDescending)
734     {
735         [o_outline_view setIndicatorImage:o_descendingSortingImage
736                                                         inTableColumn:o_tc];
737     }
738     else
739     {
740         [o_outline_view setIndicatorImage:o_ascendingSortingImage
741                                                         inTableColumn:o_tc];
742     }
743 }
744
745 @end
746
747 @implementation VLCPlaylist (NSOutlineViewDataSource)
748
749 /* return the number of children for Obj-C pointer item */ /* DONE */
750 - (int)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
751 {
752     int i_return = 0;
753     playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
754                                                        FIND_ANYWHERE );
755     if( p_playlist == NULL )
756         return 0;
757
758     if( item == nil )
759     {
760         /* root object */
761         playlist_view_t *p_view;
762         p_view = playlist_ViewFind( p_playlist, VIEW_SIMPLE );
763         if( p_view && p_view->p_root )
764             i_return = p_view->p_root->i_children;
765     }
766     else
767     {
768         playlist_item_t *p_item = (playlist_item_t *)[item pointerValue];
769         if( p_item )
770             i_return = p_item->i_children;
771     }
772     vlc_object_release( p_playlist );
773     if( i_return == -1 ) i_return = 0;
774     msg_Dbg( p_playlist, "I have %d children", i_return );
775     return i_return;
776 }
777
778 /* return the child at index for the Obj-C pointer item */ /* DONE */
779 - (id)outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item
780 {
781     playlist_item_t *p_return = NULL;
782     playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
783                                                        FIND_ANYWHERE );
784     NSValue * o_value;
785
786     if( p_playlist == NULL )
787         return nil;
788
789     if( item == nil )
790     {
791         /* root object */
792         playlist_view_t *p_view;
793         p_view = playlist_ViewFind( p_playlist, VIEW_SIMPLE );
794         if( p_view && index < p_view->p_root->i_children )
795             p_return = p_view->p_root->pp_children[index];
796     }
797     else
798     {
799         playlist_item_t *p_item = (playlist_item_t *)[item pointerValue];
800         if( p_item && index < p_item->i_children )
801         {
802             p_return = p_item->pp_children[index];
803         }
804     }
805
806     [o_status_field setStringValue: [NSString stringWithFormat:
807                         _NS("%i items in playlist"), p_playlist->i_size]];
808
809     vlc_object_release( p_playlist );
810     msg_Dbg( p_playlist, "childitem with index %d", index );
811
812     o_value = [NSValue valueWithPointer: p_return];
813
814     [o_outline_dict setObject:o_value forKey:[NSString stringWithFormat:@"%p",                                                                      p_return]];
815     return o_value;
816 }
817
818 /* is the item expandable */
819 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
820 {
821     int i_return = 0;
822     playlist_t * p_playlist = vlc_object_find( VLCIntf, VLC_OBJECT_PLAYLIST,
823                                                        FIND_ANYWHERE );
824     if( p_playlist == NULL )
825         return NO;
826
827     if( item == nil )
828     {
829         /* root object */
830         playlist_view_t *p_view;
831         p_view = playlist_ViewFind( p_playlist, VIEW_SIMPLE );
832         if( p_view && p_view->p_root )
833             i_return = p_view->p_root->i_children;
834     }
835     else
836     {
837         playlist_item_t *p_item = (playlist_item_t *)[item pointerValue];
838         if( p_item )
839             i_return = p_item->i_children;
840     }
841     vlc_object_release( p_playlist );
842
843     if( i_return == -1 || i_return == 0 )
844         return NO;
845     else
846         return YES;
847 }
848
849 /* retrieve the string values for the cells */
850 - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)o_tc byItem:(id)item
851 {
852     id o_value = nil;
853     intf_thread_t * p_intf = VLCIntf;
854     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
855                                                FIND_ANYWHERE );
856     playlist_item_t *p_item = (playlist_item_t *)[item pointerValue];
857
858     if( p_playlist == NULL || p_item == NULL )
859     {
860         return( @"error" );
861     }
862
863     if( [[o_tc identifier] isEqualToString:@"1"] )
864     {
865         o_value = [NSString stringWithUTF8String:
866             p_item->input.psz_name];
867         if( o_value == NULL )
868             o_value = [NSString stringWithCString:
869                 p_item->input.psz_name];
870     }
871     else if( [[o_tc identifier] isEqualToString:@"2"] )
872     {
873         char *psz_temp;
874         psz_temp = playlist_ItemGetInfo( p_item ,_("Meta-information"),_("Artist") );
875
876         if( psz_temp == NULL )
877             o_value = @"";
878         else
879         {
880             o_value = [NSString stringWithUTF8String: psz_temp];
881             if( o_value == NULL )
882             {
883                 o_value = [NSString stringWithCString: psz_temp];
884             }
885             free( psz_temp );
886         }
887     }
888     else if( [[o_tc identifier] isEqualToString:@"3"] )
889     {
890         char psz_duration[MSTRTIME_MAX_SIZE];
891         mtime_t dur = p_item->input.i_duration;
892         if( dur != -1 )
893         {
894             secstotimestr( psz_duration, dur/1000000 );
895             o_value = [NSString stringWithUTF8String: psz_duration];
896         }
897         else
898         {
899             o_value = @"-:--:--";
900         }
901     }
902
903     vlc_object_release( p_playlist );
904
905     return( o_value );
906 }
907
908 /* Required for drag & drop and reordering */
909 - (BOOL)outlineView:(NSOutlineView *)outlineView writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pboard
910 {
911     return NO;
912 }
913
914 - (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id <NSDraggingInfo>)info proposedItem:(id)item proposedChildIndex:(int)index
915 {
916     return NSDragOperationNone;
917 }
918
919 /* Delegate method of NSWindow */
920 - (void)windowWillClose:(NSNotification *)aNotification
921 {
922     [o_btn_playlist setState: NSOffState];
923 }
924
925 @end
926
927