]> git.sesse.net Git - vlc/blob - modules/gui/macosx/playlist.m
macosx: fix resume playback for file urls containing whitespaces
[vlc] / modules / gui / macosx / playlist.m
1 /*****************************************************************************
2  * playlist.m: MacOS X interface module
3  *****************************************************************************
4 * Copyright (C) 2002-2014 VLC authors and VideoLAN
5  * $Id$
6  *
7  * Authors: Jon Lech Johansen <jon-vl@nanocrew.net>
8  *          Derk-Jan Hartman <hartman at videola/n dot org>
9  *          Benjamin Pracht <bigben at videolab dot org>
10  *          Felix Paul Kühne <fkuehne at videolan dot org>
11  *
12  * This program is free software; you can redistribute it and/or modify
13  * it under the terms of the GNU General Public License as published by
14  * the Free Software Foundation; either version 2 of the License, or
15  * (at your option) any later version.
16  *
17  * This program is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  * GNU General Public License for more details.
21  *
22  * You should have received a copy of the GNU General Public License
23  * along with this program; if not, write to the Free Software
24  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
25  *****************************************************************************/
26
27 /* TODO
28  * add 'icons' for different types of nodes? (http://www.cocoadev.com/index.pl?IconAndTextInTableCell)
29  * reimplement enable/disable item
30  */
31
32
33 /*****************************************************************************
34  * Preamble
35  *****************************************************************************/
36 #include <stdlib.h>                                      /* malloc(), free() */
37 #include <sys/param.h>                                    /* for MAXPATHLEN */
38 #include <string.h>
39 #include <math.h>
40 #include <sys/mount.h>
41
42 #import "CompatibilityFixes.h"
43
44 #import "intf.h"
45 #import "wizard.h"
46 #import "bookmarks.h"
47 #import "playlistinfo.h"
48 #import "playlist.h"
49 #import "controls.h"
50 #import "misc.h"
51 #import "open.h"
52 #import "MainMenu.h"
53 #import "CoreInteraction.h"
54
55 #include <vlc_keys.h>
56 #import <vlc_interface.h>
57 #include <vlc_url.h>
58
59 /*****************************************************************************
60  * VLCPlaylistView implementation
61  *****************************************************************************/
62 @implementation VLCPlaylistView
63
64 - (NSMenu *)menuForEvent:(NSEvent *)o_event
65 {
66     return([(VLCPlaylist *)[self delegate] menuForEvent: o_event]);
67 }
68
69 - (void)keyDown:(NSEvent *)o_event
70 {
71     unichar key = 0;
72
73     if ([[o_event characters] length])
74         key = [[o_event characters] characterAtIndex: 0];
75
76     switch(key) {
77         case NSDeleteCharacter:
78         case NSDeleteFunctionKey:
79         case NSDeleteCharFunctionKey:
80         case NSBackspaceCharacter:
81             [(VLCPlaylist *)[self delegate] deleteItem:self];
82             break;
83
84         case NSEnterCharacter:
85         case NSCarriageReturnCharacter:
86             [(VLCPlaylist *)[[VLCMain sharedInstance] playlist] playItem:nil];
87             break;
88
89         default:
90             [super keyDown: o_event];
91             break;
92     }
93 }
94
95 - (BOOL)validateMenuItem:(NSMenuItem *)item
96 {
97     if (([self numberOfSelectedRows] >= 1 && [item action] == @selector(delete:)) || [item action] == @selector(selectAll:))
98         return YES;
99
100     return NO;
101 }
102
103 - (BOOL)acceptsFirstResponder
104 {
105     return YES;
106 }
107
108 - (BOOL)becomeFirstResponder
109 {
110     [self setNeedsDisplay:YES];
111     return YES;
112 }
113
114 - (BOOL)resignFirstResponder
115 {
116     [self setNeedsDisplay:YES];
117     return YES;
118 }
119
120 - (IBAction)delete:(id)sender
121 {
122     [[[VLCMain sharedInstance] playlist] deleteItem: sender];
123 }
124
125 @end
126
127 /*****************************************************************************
128  * VLCPlaylistCommon implementation
129  *
130  * This class the superclass of the VLCPlaylist and VLCPlaylistWizard.
131  * It contains the common methods and elements of these 2 entities.
132  *****************************************************************************/
133 @interface VLCPlaylistCommon ()
134 {
135     playlist_item_t * p_current_root_item;
136 }
137 @end
138
139 @implementation VLCPlaylistCommon
140
141 - (id)init
142 {
143     playlist_t * p_playlist = pl_Get(VLCIntf);
144     p_current_root_item = p_playlist->p_local_category;
145
146     self = [super init];
147     if (self != nil)
148         o_outline_dict = [[NSMutableDictionary alloc] init];
149
150     return self;
151 }
152
153 - (void)awakeFromNib
154 {
155     playlist_t * p_playlist = pl_Get(VLCIntf);
156     [o_outline_view setTarget: self];
157     [o_outline_view setDelegate: self];
158     [o_outline_view setDataSource: self];
159     [o_outline_view setAllowsEmptySelection: NO];
160     [o_outline_view expandItem: [o_outline_view itemAtRow:0]];
161
162     [o_outline_view_other setTarget: self];
163     [o_outline_view_other setDelegate: self];
164     [o_outline_view_other setDataSource: self];
165     [o_outline_view_other setAllowsEmptySelection: NO];
166
167     [[o_tc_name_other headerCell] setStringValue:_NS("Name")];
168     [[o_tc_author_other headerCell] setStringValue:_NS("Author")];
169     [[o_tc_duration_other headerCell] setStringValue:_NS("Duration")];
170
171     [self reloadStyles];
172 }
173
174 - (void)setPlaylistRoot: (playlist_item_t *)root_item
175 {
176     p_current_root_item = root_item;
177     [o_outline_view reloadData];
178     [o_outline_view_other reloadData];
179 }
180
181 - (playlist_item_t *)currentPlaylistRoot
182 {
183     return p_current_root_item;
184 }
185
186 - (NSOutlineView *)outlineView
187 {
188     return o_outline_view;
189 }
190
191 - (playlist_item_t *)selectedPlaylistItem
192 {
193     return [[o_outline_view itemAtRow: [o_outline_view selectedRow]]
194                                                                 pointerValue];
195 }
196
197 - (void)reloadStyles
198 {
199     NSFont *fontToUse;
200     CGFloat rowHeight;
201     if (config_GetInt(VLCIntf, "macosx-large-text")) {
202         fontToUse = [NSFont systemFontOfSize:13.];
203         rowHeight = 21.;
204     } else {
205         fontToUse = [NSFont systemFontOfSize:11.];
206         rowHeight = 16.;
207     }
208
209     NSArray *columns = [o_outline_view tableColumns];
210     NSUInteger count = columns.count;
211     for (NSUInteger x = 0; x < count; x++)
212         [[columns[x] dataCell] setFont:fontToUse];
213     [o_outline_view setRowHeight:rowHeight];
214
215     columns = [o_outline_view_other tableColumns];
216     count = columns.count;
217     for (NSUInteger x = 0; x < count; x++)
218         [[columns[x] dataCell] setFont:fontToUse];
219     [o_outline_view_other setRowHeight:rowHeight];
220 }
221
222 - (void)dealloc {
223     [o_outline_dict release];
224     [super dealloc];
225 }
226
227 @end
228
229 @implementation VLCPlaylistCommon (NSOutlineViewDataSource)
230 /* return the number of children for Obj-C pointer item */ /* DONE */
231 - (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
232 {
233     int i_return = 0;
234     playlist_item_t *p_item = NULL;
235     playlist_t * p_playlist = pl_Get(VLCIntf);
236     //assert(outlineView == o_outline_view);
237
238     PL_LOCK;
239     if (!item)
240         p_item = p_current_root_item;
241     else
242         p_item = (playlist_item_t *)[item pointerValue];
243
244     if (p_item)
245         i_return = p_item->i_children;
246     PL_UNLOCK;
247
248     return i_return > 0 ? i_return : 0;
249 }
250
251 /* return the child at index for the Obj-C pointer item */ /* DONE */
252 - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item
253 {
254     playlist_item_t *p_return = NULL, *p_item = NULL;
255     NSValue *o_value;
256     playlist_t * p_playlist = pl_Get(VLCIntf);
257
258     PL_LOCK;
259     if (item == nil)
260         p_item = p_current_root_item; /* root object */
261     else
262         p_item = (playlist_item_t *)[item pointerValue];
263
264     if (p_item && index < p_item->i_children && index >= 0)
265         p_return = p_item->pp_children[index];
266     PL_UNLOCK;
267
268     o_value = [o_outline_dict objectForKey:[NSString stringWithFormat: @"%p", p_return]];
269
270     if (o_value == nil) {
271         /* FIXME: Why is there a warning if that happens all the time and seems
272          * to be normal? Add an assert and fix it.
273          * msg_Warn(VLCIntf, "playlist item misses pointer value, adding one"); */
274         o_value = [[NSValue valueWithPointer: p_return] retain];
275     }
276     return o_value;
277 }
278
279 /* is the item expandable */
280 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
281 {
282     int i_return = 0;
283     playlist_t *p_playlist = pl_Get(VLCIntf);
284
285     PL_LOCK;
286     if (item == nil) {
287         /* root object */
288         if (p_current_root_item) {
289             i_return = p_current_root_item->i_children;
290         }
291     } else {
292         playlist_item_t *p_item = (playlist_item_t *)[item pointerValue];
293         if (p_item)
294             i_return = p_item->i_children;
295     }
296     PL_UNLOCK;
297
298     return (i_return >= 0);
299 }
300
301 /* retrieve the string values for the cells */
302 - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)o_tc byItem:(id)item
303 {
304     id o_value = nil;
305     char * psz_value;
306     playlist_item_t *p_item;
307
308     /* For error handling */
309     static BOOL attempted_reload = NO;
310
311     if (item == nil || ![item isKindOfClass: [NSValue class]]) {
312         /* Attempt to fix the error by asking for a data redisplay
313          * This might cause infinite loop, so add a small check */
314         if (!attempted_reload) {
315             attempted_reload = YES;
316             [outlineView reloadData];
317         }
318         return @"error" ;
319     }
320
321     p_item = (playlist_item_t *)[item pointerValue];
322     if (!p_item || !p_item->p_input) {
323         /* Attempt to fix the error by asking for a data redisplay
324          * This might cause infinite loop, so add a small check */
325         if (!attempted_reload) {
326             attempted_reload = YES;
327             [outlineView reloadData];
328         }
329         return @"error";
330     }
331
332     attempted_reload = NO;
333     NSString * o_identifier = [o_tc identifier];
334
335     if ([o_identifier isEqualToString:TRACKNUM_COLUMN]) {
336         psz_value = input_item_GetTrackNumber(p_item->p_input);
337         if (psz_value) {
338             o_value = [NSString stringWithUTF8String:psz_value];
339             free(psz_value);
340         }
341     } else if ([o_identifier isEqualToString:TITLE_COLUMN]) {
342         /* sanity check to prevent the NSString class from crashing */
343         char *psz_title =  input_item_GetTitleFbName(p_item->p_input);
344         if (psz_title) {
345             o_value = [NSString stringWithUTF8String:psz_title];
346             free(psz_title);
347         }
348     } else if ([o_identifier isEqualToString:ARTIST_COLUMN]) {
349         psz_value = input_item_GetArtist(p_item->p_input);
350         if (psz_value) {
351             o_value = [NSString stringWithUTF8String:psz_value];
352             free(psz_value);
353         }
354     } else if ([o_identifier isEqualToString:@"duration"]) {
355         char psz_duration[MSTRTIME_MAX_SIZE];
356         mtime_t dur = input_item_GetDuration(p_item->p_input);
357         if (dur != -1) {
358             secstotimestr(psz_duration, dur/1000000);
359             o_value = [NSString stringWithUTF8String:psz_duration];
360         }
361         else
362             o_value = @"--:--";
363     } else if ([o_identifier isEqualToString:GENRE_COLUMN]) {
364         psz_value = input_item_GetGenre(p_item->p_input);
365         if (psz_value) {
366             o_value = [NSString stringWithUTF8String:psz_value];
367             free(psz_value);
368         }
369     } else if ([o_identifier isEqualToString:ALBUM_COLUMN]) {
370         psz_value = input_item_GetAlbum(p_item->p_input);
371         if (psz_value) {
372             o_value = [NSString stringWithUTF8String:psz_value];
373             free(psz_value);
374         }
375     } else if ([o_identifier isEqualToString:DESCRIPTION_COLUMN]) {
376         psz_value = input_item_GetDescription(p_item->p_input);
377         if (psz_value) {
378             o_value = [NSString stringWithUTF8String:psz_value];
379             free(psz_value);
380         }
381     } else if ([o_identifier isEqualToString:DATE_COLUMN]) {
382         psz_value = input_item_GetDate(p_item->p_input);
383         if (psz_value) {
384             o_value = [NSString stringWithUTF8String:psz_value];
385             free(psz_value);
386         }
387     } else if ([o_identifier isEqualToString:LANGUAGE_COLUMN]) {
388         psz_value = input_item_GetLanguage(p_item->p_input);
389         if (psz_value) {
390             o_value = [NSString stringWithUTF8String:psz_value];
391             free(psz_value);
392         }
393     }
394     else if ([o_identifier isEqualToString:URI_COLUMN]) {
395         psz_value = decode_URI(input_item_GetURI(p_item->p_input));
396         if (psz_value) {
397             o_value = [NSString stringWithUTF8String:psz_value];
398             free(psz_value);
399         }
400     }
401     else if ([o_identifier isEqualToString:FILESIZE_COLUMN]) {
402         psz_value = input_item_GetURI(p_item->p_input);
403         o_value = @"";
404         if (psz_value) {
405             NSURL *url = [NSURL URLWithString:[NSString stringWithUTF8String:psz_value]];
406             if ([url isFileURL]) {
407                 NSFileManager *fileManager = [NSFileManager defaultManager];
408                 if ([fileManager fileExistsAtPath:[url path]]) {
409                     NSError *error;
410                     NSDictionary *attributes = [fileManager attributesOfItemAtPath:[url path] error:&error];
411                     o_value = [VLCByteCountFormatter stringFromByteCount:[attributes fileSize] countStyle:NSByteCountFormatterCountStyleDecimal];
412                 }
413             }
414             free(psz_value);
415         }
416     }
417     else if ([o_identifier isEqualToString:@"status"]) {
418         if (input_item_HasErrorWhenReading(p_item->p_input)) {
419             o_value = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kAlertCautionIcon)];
420             [o_value setSize: NSMakeSize(16,16)];
421         }
422     }
423
424     return o_value;
425 }
426
427 @end
428
429 /*****************************************************************************
430  * VLCPlaylistWizard implementation
431  *****************************************************************************/
432 @implementation VLCPlaylistWizard
433
434 - (IBAction)reloadOutlineView
435 {
436     /* Only reload the outlineview if the wizard window is open since this can
437        be quite long on big playlists */
438     if ([[o_outline_view window] isVisible])
439         [o_outline_view reloadData];
440 }
441
442 @end
443
444 /*****************************************************************************
445  * An extension to NSOutlineView's interface to fix compilation warnings
446  * and let us access these 2 functions properly.
447  * This uses a private API, but works fine on all current OSX releases.
448  * Radar ID 11739459 request a public API for this. However, it is probably
449  * easier and faster to recreate similar looking bitmaps ourselves.
450  *****************************************************************************/
451
452 @interface NSOutlineView (UndocumentedSortImages)
453 + (NSImage *)_defaultTableHeaderSortImage;
454 + (NSImage *)_defaultTableHeaderReverseSortImage;
455 @end
456
457
458 /*****************************************************************************
459  * VLCPlaylist implementation
460  *****************************************************************************/
461 @interface VLCPlaylist ()
462 {
463     NSImage *o_descendingSortingImage;
464     NSImage *o_ascendingSortingImage;
465
466     NSMutableArray *o_nodes_array;
467     NSMutableArray *o_items_array;
468
469     BOOL b_selected_item_met;
470     BOOL b_isSortDescending;
471     id o_tc_sortColumn;
472     NSInteger retainedRowSelection;
473
474     BOOL b_playlistmenu_nib_loaded;
475     BOOL b_view_setup;
476 }
477
478 - (void)saveTableColumns;
479 @end
480
481 @implementation VLCPlaylist
482
483 + (void)initialize
484 {
485     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
486     NSMutableArray *o_columnArray = [[NSMutableArray alloc] init];
487     [o_columnArray addObject: [NSArray arrayWithObjects:TITLE_COLUMN, [NSNumber numberWithFloat:190.], nil]];
488     [o_columnArray addObject: [NSArray arrayWithObjects:ARTIST_COLUMN, [NSNumber numberWithFloat:95.], nil]];
489     [o_columnArray addObject: [NSArray arrayWithObjects:DURATION_COLUMN, [NSNumber numberWithFloat:95.], nil]];
490
491     NSDictionary *appDefaults = [NSDictionary dictionaryWithObjectsAndKeys:
492                                  [NSArray arrayWithArray:o_columnArray], @"PlaylistColumnSelection",
493                                  [NSArray array], @"recentlyPlayedMediaList",
494                                  [NSDictionary dictionary], @"recentlyPlayedMedia", nil];
495
496     [defaults registerDefaults:appDefaults];
497     [o_columnArray release];
498 }
499
500 - (id)init
501 {
502     self = [super init];
503     if (self != nil) {
504         o_nodes_array = [[NSMutableArray alloc] init];
505         o_items_array = [[NSMutableArray alloc] init];
506     }
507     return self;
508 }
509
510 - (void)dealloc
511 {
512     [o_nodes_array release];
513     [o_items_array release];
514     [super dealloc];
515 }
516
517 - (void)awakeFromNib
518 {
519     if (b_view_setup)
520         return;
521
522     playlist_t * p_playlist = pl_Get(VLCIntf);
523
524     [super awakeFromNib];
525     [self initStrings];
526
527     [o_outline_view setDoubleAction: @selector(playItem:)];
528     [o_outline_view_other setDoubleAction: @selector(playItem:)];
529
530     [o_outline_view registerForDraggedTypes: [NSArray arrayWithObjects:NSFilenamesPboardType, @"VLCPlaylistItemPboardType", nil]];
531     [o_outline_view setIntercellSpacing: NSMakeSize (0.0, 1.0)];
532
533     [o_outline_view_other registerForDraggedTypes: [NSArray arrayWithObjects:NSFilenamesPboardType, @"VLCPlaylistItemPboardType", nil]];
534     [o_outline_view_other setIntercellSpacing: NSMakeSize (0.0, 1.0)];
535
536     /* This uses a private API, but works fine on all current OSX releases.
537      * Radar ID 11739459 request a public API for this. However, it is probably
538      * easier and faster to recreate similar looking bitmaps ourselves. */
539     o_ascendingSortingImage = [[NSOutlineView class] _defaultTableHeaderSortImage];
540     o_descendingSortingImage = [[NSOutlineView class] _defaultTableHeaderReverseSortImage];
541
542     o_tc_sortColumn = nil;
543
544     NSArray * o_columnArray = [[NSUserDefaults standardUserDefaults] arrayForKey:@"PlaylistColumnSelection"];
545     NSUInteger count = [o_columnArray count];
546
547     id o_menu = [[VLCMain sharedInstance] mainMenu];
548     NSString * o_column;
549
550     NSMenu *o_context_menu = [o_menu setupPlaylistTableColumnsMenu];
551     [o_playlist_header setMenu: o_context_menu];
552
553     for (NSUInteger i = 0; i < count; i++) {
554         o_column = [[o_columnArray objectAtIndex:i] objectAtIndex:0];
555         if ([o_column isEqualToString:@"status"])
556             continue;
557
558         if(![o_menu setPlaylistColumnTableState: NSOnState forColumn: o_column])
559             continue;
560
561         [[o_outline_view tableColumnWithIdentifier: o_column] setWidth: [[[o_columnArray objectAtIndex:i] objectAtIndex:1] floatValue]];
562     }
563
564     [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(applicationWillTerminate:) name: NSApplicationWillTerminateNotification object: nil];
565
566     b_view_setup = YES;
567 }
568
569 - (void)applicationWillTerminate:(NSNotification *)notification
570 {
571     /* let's make sure we save the correct widths and positions, since this likely changed since the last time the user played with the column selection */
572     [self saveTableColumns];
573 }
574
575 - (void)searchfieldChanged:(NSNotification *)o_notification
576 {
577     [o_search_field setStringValue:[[o_notification object] stringValue]];
578 }
579
580 - (void)initStrings
581 {
582     [o_mi_play setTitle: _NS("Play")];
583     [o_mi_delete setTitle: _NS("Delete")];
584     [o_mi_recursive_expand setTitle: _NS("Expand Node")];
585     [o_mi_selectall setTitle: _NS("Select All")];
586     [o_mi_info setTitle: _NS("Media Information...")];
587     [o_mi_dl_cover_art setTitle: _NS("Download Cover Art")];
588     [o_mi_preparse setTitle: _NS("Fetch Meta Data")];
589     [o_mi_revealInFinder setTitle: _NS("Reveal in Finder")];
590     [o_mm_mi_revealInFinder setTitle: _NS("Reveal in Finder")];
591     [[o_mm_mi_revealInFinder menu] setAutoenablesItems: NO];
592     [o_mi_sort_name setTitle: _NS("Sort Node by Name")];
593     [o_mi_sort_author setTitle: _NS("Sort Node by Author")];
594
595     [o_search_field setToolTip: _NS("Search in Playlist")];
596 }
597
598 - (void)playlistUpdated
599 {
600     /* Clear indications of any existing column sorting */
601     NSUInteger count = [[o_outline_view tableColumns] count];
602     for (NSUInteger i = 0 ; i < count ; i++)
603         [o_outline_view setIndicatorImage:nil inTableColumn: [[o_outline_view tableColumns] objectAtIndex:i]];
604
605     [o_outline_view setHighlightedTableColumn:nil];
606     o_tc_sortColumn = nil;
607     // TODO Find a way to keep the dict size to a minimum
608     //[o_outline_dict removeAllObjects];
609     [o_outline_view reloadData];
610     [[[[VLCMain sharedInstance] wizard] playlistWizard] reloadOutlineView];
611     [[[[VLCMain sharedInstance] bookmarks] dataTable] reloadData];
612
613     [o_outline_view selectRowIndexes:[NSIndexSet indexSetWithIndex:retainedRowSelection] byExtendingSelection:NO];
614
615     [self outlineViewSelectionDidChange: nil];
616     [[VLCMain sharedInstance] updateMainWindow];
617 }
618
619 - (void)outlineViewSelectionDidChange:(NSNotification *)notification
620 {
621     // FIXME: unsafe
622     playlist_item_t * p_item = [[o_outline_view itemAtRow:[o_outline_view selectedRow]] pointerValue];
623
624     if (p_item) {
625         /* update the state of our Reveal-in-Finder menu items */
626         NSMutableString *o_mrl;
627         char *psz_uri = input_item_GetURI(p_item->p_input);
628
629         [o_mi_revealInFinder setEnabled: NO];
630         [o_mm_mi_revealInFinder setEnabled: NO];
631         if (psz_uri) {
632             o_mrl = [NSMutableString stringWithUTF8String: psz_uri];
633
634             /* perform some checks whether it is a file and if it is local at all... */
635             NSRange prefix_range = [o_mrl rangeOfString: @"file:"];
636             if (prefix_range.location != NSNotFound)
637                 [o_mrl deleteCharactersInRange: prefix_range];
638
639             if ([o_mrl characterAtIndex:0] == '/') {
640                 [o_mi_revealInFinder setEnabled: YES];
641                 [o_mm_mi_revealInFinder setEnabled: YES];
642             }
643             free(psz_uri);
644         }
645
646         /* update our info-panel to reflect the new item */
647         [[[VLCMain sharedInstance] info] updatePanelWithItem:p_item->p_input];
648     }
649 }
650
651 - (BOOL)isSelectionEmpty
652 {
653     return [o_outline_view selectedRow] == -1;
654 }
655
656 - (void)updateRowSelection
657 {
658     // FIXME: unsafe
659     playlist_t *p_playlist = pl_Get(VLCIntf);
660     playlist_item_t *p_item, *p_temp_item;
661     NSMutableArray *o_array = [NSMutableArray array];
662
663     PL_LOCK;
664     p_item = playlist_CurrentPlayingItem(p_playlist);
665     if (p_item == NULL) {
666         PL_UNLOCK;
667         return;
668     }
669
670     p_temp_item = p_item;
671     while(p_temp_item->p_parent) {
672         [o_array insertObject: [NSValue valueWithPointer: p_temp_item] atIndex: 0];
673         p_temp_item = p_temp_item->p_parent;
674     }
675     PL_UNLOCK;
676
677     NSUInteger count = [o_array count];
678     for (NSUInteger j = 0; j < count - 1; j++) {
679         id o_item;
680         if ((o_item = [o_outline_dict objectForKey:
681                             [NSString stringWithFormat: @"%p",
682                             [[o_array objectAtIndex:j] pointerValue]]]) != nil) {
683             [o_outline_view expandItem: o_item];
684         }
685     }
686
687     id o_item = [o_outline_dict objectForKey:[NSString stringWithFormat: @"%p", p_item]];
688     NSInteger i_index = [o_outline_view rowForItem:o_item];
689     [o_outline_view selectRowIndexes:[NSIndexSet indexSetWithIndex:i_index] byExtendingSelection:NO];
690     [o_outline_view setNeedsDisplay:YES];
691 }
692
693 /* Check if p_item is a child of p_node recursively. We need to check the item
694    existence first since OSX sometimes tries to redraw items that have been
695    deleted. We don't do it when not required since this verification takes
696    quite a long time on big playlists (yes, pretty hacky). */
697
698 - (BOOL)isItem: (playlist_item_t *)p_item inNode: (playlist_item_t *)p_node checkItemExistence:(BOOL)b_check locked:(BOOL)b_locked
699 {
700     playlist_t * p_playlist = pl_Get(VLCIntf);
701     playlist_item_t *p_temp_item = p_item;
702
703     if (!p_node)
704         return NO;
705
706     if (p_node == p_item)
707         return YES;
708
709     if (p_node->i_children < 1)
710         return NO;
711
712     if (p_temp_item) {
713         int i;
714         if (!b_locked) PL_LOCK;
715
716         if (b_check) {
717         /* Since outlineView: willDisplayCell:... may call this function with
718            p_items that don't exist anymore, first check if the item is still
719            in the playlist. Any cleaner solution welcomed. */
720             for (i = 0; i < p_playlist->all_items.i_size; i++) {
721                 if (ARRAY_VAL(p_playlist->all_items, i) == p_item)
722                     break;
723                 else if (i == p_playlist->all_items.i_size - 1)
724                 {
725                     if (!b_locked) PL_UNLOCK;
726                     return NO;
727                 }
728             }
729         }
730
731         while(p_temp_item) {
732             p_temp_item = p_temp_item->p_parent;
733             if (p_temp_item == p_node) {
734                 if (!b_locked) PL_UNLOCK;
735                 return YES;
736             }
737         }
738         if (!b_locked) PL_UNLOCK;
739     }
740     return NO;
741 }
742
743 /* This method is useful for instance to remove the selected children of an
744    already selected node */
745 - (void)removeItemsFrom:(id)o_items ifChildrenOf:(id)o_nodes
746 {
747     NSUInteger itemCount = [o_items count];
748     NSUInteger nodeCount = [o_nodes count];
749     for (NSUInteger i = 0 ; i < itemCount ; i++) {
750         for (NSUInteger j = 0 ; j < nodeCount ; j++) {
751             if (o_items == o_nodes) {
752                 if (j == i) continue;
753             }
754             if ([self isItem: [[o_items objectAtIndex:i] pointerValue]
755                     inNode: [[o_nodes objectAtIndex:j] pointerValue]
756                     checkItemExistence: NO locked:NO]) {
757                 [o_items removeObjectAtIndex:i];
758                 /* We need to execute the next iteration with the same index
759                    since the current item has been deleted */
760                 i--;
761                 break;
762             }
763         }
764     }
765 }
766
767 - (IBAction)savePlaylist:(id)sender
768 {
769     playlist_t * p_playlist = pl_Get(VLCIntf);
770
771     NSSavePanel *o_save_panel = [NSSavePanel savePanel];
772     NSString * o_name = [NSString stringWithFormat: @"%@", _NS("Untitled")];
773
774     [NSBundle loadNibNamed:@"PlaylistAccessoryView" owner:self];
775
776     [o_save_accessory_text setStringValue: _NS("File Format:")];
777     [[o_save_accessory_popup itemAtIndex:0] setTitle: _NS("Extended M3U")];
778     [[o_save_accessory_popup itemAtIndex:1] setTitle: _NS("XML Shareable Playlist Format (XSPF)")];
779     [[o_save_accessory_popup itemAtIndex:2] setTitle: _NS("HTML playlist")];
780
781     [o_save_panel setTitle: _NS("Save Playlist")];
782     [o_save_panel setPrompt: _NS("Save")];
783     [o_save_panel setAccessoryView: o_save_accessory_view];
784     [o_save_panel setNameFieldStringValue: o_name];
785
786     if ([o_save_panel runModal] == NSFileHandlingPanelOKButton) {
787         NSString *o_filename = [[o_save_panel URL] path];
788
789         if ([o_save_accessory_popup indexOfSelectedItem] == 0) {
790             NSString * o_real_filename;
791             NSRange range;
792             range.location = [o_filename length] - [@".m3u" length];
793             range.length = [@".m3u" length];
794
795             if ([o_filename compare:@".m3u" options: NSCaseInsensitiveSearch range: range] != NSOrderedSame)
796                 o_real_filename = [NSString stringWithFormat: @"%@.m3u", o_filename];
797             else
798                 o_real_filename = o_filename;
799
800             playlist_Export(p_playlist,
801                 [o_real_filename fileSystemRepresentation],
802                 p_playlist->p_local_category, "export-m3u");
803         } else if ([o_save_accessory_popup indexOfSelectedItem] == 1) {
804             NSString * o_real_filename;
805             NSRange range;
806             range.location = [o_filename length] - [@".xspf" length];
807             range.length = [@".xspf" length];
808
809             if ([o_filename compare:@".xspf" options: NSCaseInsensitiveSearch range: range] != NSOrderedSame)
810                 o_real_filename = [NSString stringWithFormat: @"%@.xspf", o_filename];
811             else
812                 o_real_filename = o_filename;
813
814             playlist_Export(p_playlist,
815                 [o_real_filename fileSystemRepresentation],
816                 p_playlist->p_local_category, "export-xspf");
817         } else {
818             NSString * o_real_filename;
819             NSRange range;
820             range.location = [o_filename length] - [@".html" length];
821             range.length = [@".html" length];
822
823             if ([o_filename compare:@".html" options: NSCaseInsensitiveSearch range: range] != NSOrderedSame)
824                 o_real_filename = [NSString stringWithFormat: @"%@.html", o_filename];
825             else
826                 o_real_filename = o_filename;
827
828             playlist_Export(p_playlist,
829                 [o_real_filename fileSystemRepresentation],
830                 p_playlist->p_local_category, "export-html");
831         }
832     }
833 }
834
835 /* When called retrieves the selected outlineview row and plays that node or item */
836 - (IBAction)playItem:(id)sender
837 {
838     intf_thread_t * p_intf = VLCIntf;
839     playlist_t * p_playlist = pl_Get(p_intf);
840
841     playlist_item_t *p_item;
842     playlist_item_t *p_node = NULL;
843
844     // ignore clicks on column header when handling double action
845     if (sender != nil && [o_outline_view clickedRow] == -1 && sender != o_mi_play)
846         return;
847
848     p_item = [[o_outline_view itemAtRow:[o_outline_view selectedRow]] pointerValue];
849
850     PL_LOCK;
851     if (p_item) {
852         if (p_item->i_children == -1) {
853             p_node = p_item->p_parent;
854         } else {
855             p_node = p_item;
856             if (p_node->i_children > 0 && p_node->pp_children[0]->i_children == -1)
857                 p_item = p_node->pp_children[0];
858             else
859                 p_item = NULL;
860         }
861
862         playlist_Control(p_playlist, PLAYLIST_VIEWPLAY, pl_Locked, p_node, p_item);
863     }
864     PL_UNLOCK;
865 }
866
867 - (IBAction)revealItemInFinder:(id)sender
868 {
869     NSIndexSet * selectedRows = [o_outline_view selectedRowIndexes];
870     NSUInteger count = [selectedRows count];
871     NSUInteger indexes[count];
872     [selectedRows getIndexes:indexes maxCount:count inIndexRange:nil];
873
874     NSMutableString * o_mrl;
875     playlist_item_t *p_item;
876     for (NSUInteger i = 0; i < count; i++) {
877         p_item = [[o_outline_view itemAtRow:indexes[i]] pointerValue];
878
879         if (! p_item || !p_item->p_input)
880             continue;
881
882         char * psz_url = decode_URI(input_item_GetURI(p_item->p_input));
883         o_mrl = [[NSMutableString alloc] initWithString: [NSString stringWithUTF8String:psz_url ? psz_url : ""]];
884         if (psz_url != NULL)
885             free( psz_url );
886
887         /* perform some checks whether it is a file and if it is local at all... */
888         if ([o_mrl length] > 0) {
889             NSRange prefix_range = [o_mrl rangeOfString: @"file:"];
890             if (prefix_range.location != NSNotFound)
891                 [o_mrl deleteCharactersInRange: prefix_range];
892
893             if ([o_mrl characterAtIndex:0] == '/')
894                 [[NSWorkspace sharedWorkspace] selectFile: o_mrl inFileViewerRootedAtPath: o_mrl];
895         }
896
897         [o_mrl release];
898     }
899 }
900
901 /* When called retrieves the selected outlineview row and plays that node or item */
902 - (IBAction)preparseItem:(id)sender
903 {
904     int i_count;
905     NSIndexSet *o_selected_indexes;
906     intf_thread_t * p_intf = VLCIntf;
907     playlist_t * p_playlist = pl_Get(p_intf);
908     playlist_item_t *p_item = NULL;
909
910     o_selected_indexes = [o_outline_view selectedRowIndexes];
911     i_count = [o_selected_indexes count];
912
913     NSUInteger indexes[i_count];
914     [o_selected_indexes getIndexes:indexes maxCount:i_count inIndexRange:nil];
915     for (int i = 0; i < i_count; i++) {
916         p_item = [[o_outline_view itemAtRow:indexes[i]] pointerValue];
917         [o_outline_view deselectRow: indexes[i]];
918
919         if (p_item) {
920             if (p_item->i_children == -1)
921                 libvlc_MetaRequest(p_intf->p_libvlc, p_item->p_input, META_REQUEST_OPTION_NONE);
922             else
923                 msg_Dbg(p_intf, "preparsing nodes not implemented");
924         }
925     }
926     [self playlistUpdated];
927 }
928
929 - (IBAction)downloadCoverArt:(id)sender
930 {
931     int i_count;
932     NSIndexSet *o_selected_indexes;
933     intf_thread_t * p_intf = VLCIntf;
934     playlist_t * p_playlist = pl_Get(p_intf);
935     playlist_item_t *p_item = NULL;
936
937     o_selected_indexes = [o_outline_view selectedRowIndexes];
938     i_count = [o_selected_indexes count];
939
940     NSUInteger indexes[i_count];
941     [o_selected_indexes getIndexes:indexes maxCount:i_count inIndexRange:nil];
942     for (int i = 0; i < i_count; i++) {
943         p_item = [[o_outline_view itemAtRow: indexes[i]] pointerValue];
944
945         if (p_item && p_item->i_children == -1)
946             libvlc_ArtRequest(p_intf->p_libvlc, p_item->p_input, META_REQUEST_OPTION_NONE);
947     }
948     [self playlistUpdated];
949 }
950
951 - (IBAction)selectAll:(id)sender
952 {
953     [o_outline_view selectAll: nil];
954 }
955
956 - (IBAction)showInfoPanel:(id)sender
957 {
958     [[[VLCMain sharedInstance] info] initPanel];
959 }
960
961 - (IBAction)deleteItem:(id)sender
962 {
963     int i_count;
964     NSIndexSet *o_selected_indexes;
965     playlist_t * p_playlist;
966     intf_thread_t * p_intf = VLCIntf;
967
968     o_selected_indexes = [o_outline_view selectedRowIndexes];
969     i_count = [o_selected_indexes count];
970     retainedRowSelection = [o_selected_indexes firstIndex];
971
972     p_playlist = pl_Get(p_intf);
973
974     NSUInteger indexes[i_count];
975     if (i_count == [o_outline_view numberOfRows]) {
976         PL_LOCK;
977         playlist_NodeDelete(p_playlist, [self currentPlaylistRoot], true, false);
978         PL_UNLOCK;
979         [self playlistUpdated];
980         return;
981     }
982     [o_selected_indexes getIndexes:indexes maxCount:i_count inIndexRange:nil];
983     for (int i = 0; i < i_count; i++) {
984         id o_item = [o_outline_view itemAtRow: indexes[i]];
985         [o_outline_view deselectRow: indexes[i]];
986
987         PL_LOCK;
988         playlist_item_t *p_item = [o_item pointerValue];
989         if (!p_item || !p_item->p_input) {
990             PL_UNLOCK;
991             continue;
992         }
993
994         if (p_item->i_children != -1) {
995         //is a node and not an item
996             if (playlist_Status(p_playlist) != PLAYLIST_STOPPED &&
997                 [self isItem: playlist_CurrentPlayingItem(p_playlist) inNode: ((playlist_item_t *)[o_item pointerValue])
998                         checkItemExistence: NO locked:YES] == YES)
999                 // if current item is in selected node and is playing then stop playlist
1000                 playlist_Control(p_playlist, PLAYLIST_STOP, pl_Locked);
1001
1002                 playlist_NodeDelete(p_playlist, p_item, true, false);
1003         } else
1004             playlist_DeleteFromInput(p_playlist, p_item->p_input, pl_Locked);
1005
1006         PL_UNLOCK;
1007         [o_outline_dict removeObjectForKey:[NSString stringWithFormat:@"%p", [o_item pointerValue]]];
1008         [o_item release];
1009     }
1010
1011     [self playlistUpdated];
1012 }
1013
1014 - (IBAction)sortNodeByName:(id)sender
1015 {
1016     [self sortNode: SORT_TITLE];
1017 }
1018
1019 - (IBAction)sortNodeByAuthor:(id)sender
1020 {
1021     [self sortNode: SORT_ARTIST];
1022 }
1023
1024 - (void)sortNode:(int)i_mode
1025 {
1026     playlist_t * p_playlist = pl_Get(VLCIntf);
1027     playlist_item_t * p_item;
1028
1029     if ([o_outline_view selectedRow] > -1) {
1030         p_item = [[o_outline_view itemAtRow: [o_outline_view selectedRow]] pointerValue];
1031         if (!p_item)
1032             return;
1033     } else
1034         p_item = [self currentPlaylistRoot]; // If no item is selected, sort the whole playlist
1035
1036     PL_LOCK;
1037     if (p_item->i_children > -1) // the item is a node
1038         playlist_RecursiveNodeSort(p_playlist, p_item, i_mode, ORDER_NORMAL);
1039     else
1040         playlist_RecursiveNodeSort(p_playlist, p_item->p_parent, i_mode, ORDER_NORMAL);
1041
1042     PL_UNLOCK;
1043     [self playlistUpdated];
1044 }
1045
1046 - (input_item_t *)createItem:(NSDictionary *)o_one_item
1047 {
1048     intf_thread_t * p_intf = VLCIntf;
1049     playlist_t * p_playlist = pl_Get(p_intf);
1050
1051     input_item_t *p_input;
1052     BOOL b_rem = FALSE, b_dir = FALSE, b_writable = FALSE;
1053     NSString *o_uri, *o_name, *o_path;
1054     NSURL * o_nsurl;
1055     NSArray *o_options;
1056     NSURL *o_true_file;
1057
1058     /* Get the item */
1059     o_uri = (NSString *)[o_one_item objectForKey: @"ITEM_URL"];
1060     o_nsurl = [NSURL URLWithString: o_uri];
1061     o_path = [o_nsurl path];
1062     o_name = (NSString *)[o_one_item objectForKey: @"ITEM_NAME"];
1063     o_options = (NSArray *)[o_one_item objectForKey: @"ITEM_OPTIONS"];
1064
1065     if ([[NSFileManager defaultManager] fileExistsAtPath:o_path isDirectory:&b_dir] && b_dir &&
1066         [[NSWorkspace sharedWorkspace] getFileSystemInfoForPath:o_path isRemovable: &b_rem
1067                                                      isWritable:&b_writable isUnmountable:NULL description:NULL type:NULL] && b_rem && !b_writable && [o_nsurl isFileURL]) {
1068         id o_vlc_open = [[VLCMain sharedInstance] open];
1069
1070         NSString *diskType = [o_vlc_open getVolumeTypeFromMountPath: o_path];
1071         msg_Dbg(p_intf, "detected optical media of type %s in the file input", [diskType UTF8String]);
1072
1073         if ([diskType isEqualToString: kVLCMediaDVD])
1074             o_uri = [NSString stringWithFormat: @"dvdnav://%@", [o_vlc_open getBSDNodeFromMountPath: o_path]];
1075         else if ([diskType isEqualToString: kVLCMediaVideoTSFolder])
1076             o_uri = [NSString stringWithFormat: @"dvdnav://%@", o_path];
1077         else if ([diskType isEqualToString: kVLCMediaAudioCD])
1078             o_uri = [NSString stringWithFormat: @"cdda://%@", [o_vlc_open getBSDNodeFromMountPath: o_path]];
1079         else if ([diskType isEqualToString: kVLCMediaVCD])
1080             o_uri = [NSString stringWithFormat: @"vcd://%@#0:0", [o_vlc_open getBSDNodeFromMountPath: o_path]];
1081         else if ([diskType isEqualToString: kVLCMediaSVCD])
1082             o_uri = [NSString stringWithFormat: @"vcd://%@@0:0", [o_vlc_open getBSDNodeFromMountPath: o_path]];
1083         else if ([diskType isEqualToString: kVLCMediaBD] || [diskType isEqualToString: kVLCMediaBDMVFolder])
1084             o_uri = [NSString stringWithFormat: @"bluray://%@", o_path];
1085         else
1086             msg_Warn(VLCIntf, "unknown disk type, treating %s as regular input", [o_path UTF8String]);
1087
1088         p_input = input_item_New([o_uri UTF8String], [[[NSFileManager defaultManager] displayNameAtPath: o_path] UTF8String]);
1089     }
1090     else
1091         p_input = input_item_New([o_uri fileSystemRepresentation], o_name ? [o_name UTF8String] : NULL);
1092
1093     if (!p_input)
1094         return NULL;
1095
1096     if (o_options) {
1097         NSUInteger count = [o_options count];
1098         for (NSUInteger i = 0; i < count; i++)
1099             input_item_AddOption(p_input, [[o_options objectAtIndex:i] UTF8String], VLC_INPUT_OPTION_TRUSTED);
1100     }
1101
1102     /* Recent documents menu */
1103     if (o_nsurl != nil && (BOOL)config_GetInt(p_playlist, "macosx-recentitems") == YES)
1104         [[NSDocumentController sharedDocumentController] noteNewRecentDocumentURL: o_nsurl];
1105
1106     return p_input;
1107 }
1108
1109 - (void)appendArray:(NSArray*)o_array atPos:(int)i_position enqueue:(BOOL)b_enqueue
1110 {
1111     playlist_t * p_playlist = pl_Get(VLCIntf);
1112     NSUInteger count = [o_array count];
1113     BOOL b_usingPlaylist;
1114     if ([self currentPlaylistRoot] == p_playlist->p_ml_category)
1115         b_usingPlaylist = NO;
1116     else
1117         b_usingPlaylist = YES;
1118
1119     PL_LOCK;
1120     for (NSUInteger i_item = 0; i_item < count; i_item++) {
1121         input_item_t *p_input;
1122         NSDictionary *o_one_item;
1123
1124         /* Get the item */
1125         o_one_item = [o_array objectAtIndex:i_item];
1126         p_input = [self createItem: o_one_item];
1127         if (!p_input)
1128             continue;
1129
1130         /* Add the item */
1131         int returnValue = playlist_AddInput(p_playlist, p_input, PLAYLIST_INSERT, i_position == -1 ? PLAYLIST_END : i_position + i_item, b_usingPlaylist, pl_Locked);
1132         if (returnValue != VLC_SUCCESS) {
1133             vlc_gc_decref(p_input);
1134             continue;
1135         }
1136
1137         if (i_item == 0 && !b_enqueue) {
1138             playlist_item_t *p_item = playlist_ItemGetByInput(p_playlist, p_input);
1139             playlist_Control(p_playlist, PLAYLIST_VIEWPLAY, pl_Locked, p_item->p_parent, p_item);
1140         }
1141
1142         vlc_gc_decref(p_input);
1143     }
1144     PL_UNLOCK;
1145     [self playlistUpdated];
1146 }
1147
1148 - (void)appendNodeArray:(NSArray*)o_array inNode:(playlist_item_t *)p_node atPos:(int)i_position enqueue:(BOOL)b_enqueue
1149 {
1150     playlist_t * p_playlist = pl_Get(VLCIntf);
1151     NSUInteger count = [o_array count];
1152
1153     for (NSUInteger i_item = 0; i_item < count; i_item++) {
1154         input_item_t *p_input;
1155         NSDictionary *o_one_item;
1156
1157         /* Get the item */
1158         PL_LOCK;
1159         o_one_item = [o_array objectAtIndex:i_item];
1160         p_input = [self createItem: o_one_item];
1161
1162         if (!p_input)
1163             continue;
1164
1165         /* Add the item */
1166         playlist_NodeAddInput(p_playlist, p_input, p_node,
1167                                       PLAYLIST_INSERT,
1168                                       i_position == -1 ?
1169                                       PLAYLIST_END : i_position + i_item,
1170                                       pl_Locked);
1171
1172
1173         if (i_item == 0 && !b_enqueue) {
1174             playlist_item_t *p_item;
1175             p_item = playlist_ItemGetByInput(p_playlist, p_input);
1176             playlist_Control(p_playlist, PLAYLIST_VIEWPLAY, pl_Locked, p_node, p_item);
1177         }
1178         PL_UNLOCK;
1179         vlc_gc_decref(p_input);
1180     }
1181     [self playlistUpdated];
1182 }
1183
1184 - (NSMutableArray *)subSearchItem:(playlist_item_t *)p_item
1185 {
1186     playlist_t *p_playlist = pl_Get(VLCIntf);
1187     playlist_item_t *p_selected_item;
1188     int i_selected_row;
1189
1190     i_selected_row = [o_outline_view selectedRow];
1191     if (i_selected_row < 0)
1192         i_selected_row = 0;
1193
1194     p_selected_item = (playlist_item_t *)[[o_outline_view itemAtRow: i_selected_row] pointerValue];
1195
1196     for (NSUInteger i_current = 0; i_current < p_item->i_children ; i_current++) {
1197         char *psz_temp;
1198         NSString *o_current_name, *o_current_author;
1199
1200         PL_LOCK;
1201         o_current_name = [NSString stringWithUTF8String:p_item->pp_children[i_current]->p_input->psz_name];
1202         psz_temp = input_item_GetInfo(p_item->p_input, _("Meta-information"),_("Artist"));
1203         o_current_author = [NSString stringWithUTF8String:psz_temp];
1204         free(psz_temp);
1205         PL_UNLOCK;
1206
1207         if (p_selected_item == p_item->pp_children[i_current] && b_selected_item_met == NO)
1208             b_selected_item_met = YES;
1209         else if (p_selected_item == p_item->pp_children[i_current] && b_selected_item_met == YES)
1210             return NULL;
1211         else if (b_selected_item_met == YES &&
1212                     ([o_current_name rangeOfString:[o_search_field
1213                         stringValue] options:NSCaseInsensitiveSearch].length ||
1214                       [o_current_author rangeOfString:[o_search_field
1215                         stringValue] options:NSCaseInsensitiveSearch].length))
1216             /*Adds the parent items in the result array as well, so that we can
1217             expand the tree*/
1218             return [NSMutableArray arrayWithObject: [NSValue valueWithPointer: p_item->pp_children[i_current]]];
1219
1220         if (p_item->pp_children[i_current]->i_children > 0) {
1221             id o_result = [self subSearchItem:
1222                                             p_item->pp_children[i_current]];
1223             if (o_result != NULL) {
1224                 [o_result insertObject: [NSValue valueWithPointer:
1225                                 p_item->pp_children[i_current]] atIndex:0];
1226                 return o_result;
1227             }
1228         }
1229     }
1230     return NULL;
1231 }
1232
1233 - (IBAction)searchItem:(id)sender
1234 {
1235     playlist_t * p_playlist = pl_Get(VLCIntf);
1236     id o_result;
1237
1238     int i_row = -1;
1239
1240     b_selected_item_met = NO;
1241
1242     /* First, only search after the selected item:
1243      * (b_selected_item_met = NO) */
1244     o_result = [self subSearchItem:[self currentPlaylistRoot]];
1245     if (o_result == NULL)
1246         /* If the first search failed, search again from the beginning */
1247         o_result = [self subSearchItem:[self currentPlaylistRoot]];
1248
1249     if (o_result != NULL) {
1250         int i_start;
1251         if ([[o_result objectAtIndex:0] pointerValue] == p_playlist->p_local_category)
1252             i_start = 1;
1253         else
1254             i_start = 0;
1255         NSUInteger count = [o_result count];
1256
1257         for (NSUInteger i = i_start ; i < count - 1 ; i++) {
1258             [o_outline_view expandItem: [o_outline_dict objectForKey:
1259                         [NSString stringWithFormat: @"%p",
1260                         [[o_result objectAtIndex:i] pointerValue]]]];
1261         }
1262         i_row = [o_outline_view rowForItem: [o_outline_dict objectForKey:
1263                         [NSString stringWithFormat: @"%p",
1264                         [[o_result objectAtIndex:count - 1 ]
1265                         pointerValue]]]];
1266     }
1267     if (i_row > -1) {
1268         [o_outline_view selectRowIndexes:[NSIndexSet indexSetWithIndex:i_row] byExtendingSelection:NO];
1269         [o_outline_view scrollRowToVisible: i_row];
1270     }
1271 }
1272
1273 - (IBAction)recursiveExpandNode:(id)sender
1274 {
1275     NSIndexSet * selectedRows = [o_outline_view selectedRowIndexes];
1276     NSUInteger count = [selectedRows count];
1277     NSUInteger indexes[count];
1278     [selectedRows getIndexes:indexes maxCount:count inIndexRange:nil];
1279
1280     id o_item;
1281     playlist_item_t *p_item;
1282     for (NSUInteger i = 0; i < count; i++) {
1283         o_item = [o_outline_view itemAtRow: indexes[i]];
1284         p_item = (playlist_item_t *)[o_item pointerValue];
1285
1286         if (![[o_outline_view dataSource] outlineView: o_outline_view isItemExpandable: o_item])
1287             o_item = [o_outline_dict objectForKey: [NSString stringWithFormat: @"%p", p_item->p_parent]];
1288
1289         /* We need to collapse the node first, since OSX refuses to recursively
1290          expand an already expanded node, even if children nodes are collapsed. */
1291         [o_outline_view collapseItem: o_item collapseChildren: YES];
1292         [o_outline_view expandItem: o_item expandChildren: YES];
1293
1294         selectedRows = [o_outline_view selectedRowIndexes];
1295         [selectedRows getIndexes:indexes maxCount:count inIndexRange:nil];
1296     }
1297 }
1298
1299 - (NSMenu *)menuForEvent:(NSEvent *)o_event
1300 {
1301     if (!b_playlistmenu_nib_loaded)
1302         b_playlistmenu_nib_loaded = [NSBundle loadNibNamed:@"PlaylistMenu" owner:self];
1303
1304     NSPoint pt;
1305     bool b_rows;
1306     bool b_item_sel;
1307
1308     pt = [o_outline_view convertPoint: [o_event locationInWindow] fromView: nil];
1309     int row = [o_outline_view rowAtPoint:pt];
1310     if (row != -1 && ![[o_outline_view selectedRowIndexes] containsIndex: row])
1311         [o_outline_view selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO];
1312
1313     b_item_sel = (row != -1 && [o_outline_view selectedRow] != -1);
1314     b_rows = [o_outline_view numberOfRows] != 0;
1315
1316     [o_mi_play setEnabled: b_item_sel];
1317     [o_mi_delete setEnabled: b_item_sel];
1318     [o_mi_selectall setEnabled: b_rows];
1319     [o_mi_info setEnabled: b_item_sel];
1320     [o_mi_preparse setEnabled: b_item_sel];
1321     [o_mi_recursive_expand setEnabled: b_item_sel];
1322     [o_mi_sort_name setEnabled: b_item_sel];
1323     [o_mi_sort_author setEnabled: b_item_sel];
1324
1325     return o_ctx_menu;
1326 }
1327
1328 - (void)outlineView: (NSOutlineView *)o_tv didClickTableColumn:(NSTableColumn *)o_tc
1329 {
1330     int i_mode, i_type = 0;
1331     intf_thread_t *p_intf = VLCIntf;
1332     NSString * o_identifier = [o_tc identifier];
1333
1334     playlist_t *p_playlist = pl_Get(p_intf);
1335
1336     if ([o_identifier isEqualToString:TRACKNUM_COLUMN])
1337         i_mode = SORT_TRACK_NUMBER;
1338     else if ([o_identifier isEqualToString:TITLE_COLUMN])
1339         i_mode = SORT_TITLE;
1340     else if ([o_identifier isEqualToString:ARTIST_COLUMN])
1341         i_mode = SORT_ARTIST;
1342     else if ([o_identifier isEqualToString:GENRE_COLUMN])
1343         i_mode = SORT_GENRE;
1344     else if ([o_identifier isEqualToString:DURATION_COLUMN])
1345         i_mode = SORT_DURATION;
1346     else if ([o_identifier isEqualToString:ALBUM_COLUMN])
1347         i_mode = SORT_ALBUM;
1348     else if ([o_identifier isEqualToString:DESCRIPTION_COLUMN])
1349         i_mode = SORT_DESCRIPTION;
1350     else if ([o_identifier isEqualToString:URI_COLUMN])
1351         i_mode = SORT_URI;
1352     else
1353         return;
1354
1355     if (o_tc_sortColumn == o_tc)
1356         b_isSortDescending = !b_isSortDescending;
1357     else
1358         b_isSortDescending = false;
1359
1360     if (b_isSortDescending)
1361         i_type = ORDER_REVERSE;
1362     else
1363         i_type = ORDER_NORMAL;
1364
1365     PL_LOCK;
1366     playlist_RecursiveNodeSort(p_playlist, [self currentPlaylistRoot], i_mode, i_type);
1367     PL_UNLOCK;
1368
1369     [self playlistUpdated];
1370
1371     o_tc_sortColumn = o_tc;
1372     [o_outline_view setHighlightedTableColumn:o_tc];
1373
1374     if (b_isSortDescending)
1375         [o_outline_view setIndicatorImage:o_descendingSortingImage inTableColumn:o_tc];
1376     else
1377         [o_outline_view setIndicatorImage:o_ascendingSortingImage inTableColumn:o_tc];
1378 }
1379
1380
1381 - (void)outlineView:(NSOutlineView *)outlineView
1382                                 willDisplayCell:(id)cell
1383                                 forTableColumn:(NSTableColumn *)tableColumn
1384                                 item:(id)item
1385 {
1386     /* this method can be called when VLC is already dead, hence the extra checks */
1387     intf_thread_t * p_intf = VLCIntf;
1388     if (!p_intf)
1389         return;
1390     playlist_t *p_playlist = pl_Get(p_intf);
1391     if (!p_playlist)
1392         return;
1393
1394     id o_playing_item;
1395
1396     PL_LOCK;
1397     o_playing_item = [o_outline_dict objectForKey: [NSString stringWithFormat:@"%p",  playlist_CurrentPlayingItem(p_playlist)]];
1398     PL_UNLOCK;
1399
1400     NSFont *fontToUse;
1401     if (config_GetInt(VLCIntf, "macosx-large-text"))
1402         fontToUse = [NSFont systemFontOfSize:13.];
1403     else
1404         fontToUse = [NSFont systemFontOfSize:11.];
1405
1406     if ([self isItem: [o_playing_item pointerValue] inNode: [item pointerValue] checkItemExistence:YES locked:NO]
1407                         || [o_playing_item isEqual: item])
1408         [cell setFont: [[NSFontManager sharedFontManager] convertFont:fontToUse toHaveTrait:NSBoldFontMask]];
1409     else
1410         [cell setFont: [[NSFontManager sharedFontManager] convertFont:fontToUse toNotHaveTrait:NSBoldFontMask]];
1411 }
1412
1413 - (id)playingItem
1414 {
1415     playlist_t *p_playlist = pl_Get(VLCIntf);
1416
1417     id o_playing_item;
1418
1419     PL_LOCK;
1420     o_playing_item = [o_outline_dict objectForKey: [NSString stringWithFormat:@"%p",  playlist_CurrentPlayingItem(p_playlist)]];
1421     PL_UNLOCK;
1422
1423     return o_playing_item;
1424 }
1425
1426 - (NSArray *)draggedItems
1427 {
1428     return [[o_nodes_array arrayByAddingObjectsFromArray: o_items_array] retain];
1429 }
1430
1431 - (void)setColumn: (NSString *)o_column state: (NSInteger)i_state translationDict:(NSDictionary *)o_dict
1432 {
1433     NSTableColumn * o_work_tc;
1434
1435     if (i_state == NSOnState) {
1436         NSString *o_title = [o_dict objectForKey:o_column];
1437         if (!o_title)
1438             return;
1439
1440         o_work_tc = [[NSTableColumn alloc] initWithIdentifier: o_column];
1441         [o_work_tc setEditable: NO];
1442         [[o_work_tc dataCell] setFont: [NSFont controlContentFontOfSize:11.]];
1443
1444         [[o_work_tc headerCell] setStringValue: [o_dict objectForKey:o_column]];
1445
1446         if ([o_column isEqualToString: TRACKNUM_COLUMN]) {
1447             [o_work_tc setWidth: 20.];
1448             [o_work_tc setResizingMask: NSTableColumnNoResizing];
1449             [[o_work_tc headerCell] setStringValue: @"#"];
1450         }
1451
1452         [o_outline_view addTableColumn: o_work_tc];
1453         [o_work_tc release];
1454         [o_outline_view reloadData];
1455         [o_outline_view setNeedsDisplay: YES];
1456     }
1457     else
1458         [o_outline_view removeTableColumn: [o_outline_view tableColumnWithIdentifier: o_column]];
1459
1460     [o_outline_view setOutlineTableColumn: [o_outline_view tableColumnWithIdentifier:TITLE_COLUMN]];
1461 }
1462
1463 - (void)saveTableColumns
1464 {
1465     NSMutableArray * o_arrayToSave = [[NSMutableArray alloc] init];
1466     NSArray * o_columns = [[NSArray alloc] initWithArray:[o_outline_view tableColumns]];
1467     NSUInteger count = [o_columns count];
1468     NSTableColumn * o_currentColumn;
1469     for (NSUInteger i = 0; i < count; i++) {
1470         o_currentColumn = [o_columns objectAtIndex:i];
1471         [o_arrayToSave addObject:[NSArray arrayWithObjects:[o_currentColumn identifier], [NSNumber numberWithFloat:[o_currentColumn width]], nil]];
1472     }
1473     [[NSUserDefaults standardUserDefaults] setObject: o_arrayToSave forKey:@"PlaylistColumnSelection"];
1474     [[NSUserDefaults standardUserDefaults] synchronize];
1475     [o_columns release];
1476     [o_arrayToSave release];
1477 }
1478
1479 - (void)continuePlaybackWhereYouLeftOff:(input_thread_t *)p_input_thread
1480 {
1481     NSDictionary *recentlyPlayedFiles = [[NSUserDefaults standardUserDefaults] objectForKey:@"recentlyPlayedMedia"];
1482     if (recentlyPlayedFiles) {
1483         input_item_t *p_item = input_GetItem(p_input_thread);
1484         if (!p_item)
1485             return;
1486
1487         /* allow the user to over-write the start-time */
1488         if (p_item->i_options > 0) {
1489             for (int x = 0; x < p_item->i_options; x++) {
1490                 if (strstr(p_item->ppsz_options[x],"start-time"))
1491                     return;
1492             }
1493         }
1494
1495         char *psz_url = decode_URI(input_item_GetURI(p_item));
1496         NSString *url = [NSString stringWithUTF8String:psz_url ? psz_url : ""];
1497         free(psz_url);
1498
1499         NSNumber *lastPosition = [recentlyPlayedFiles objectForKey:url];
1500         if (lastPosition && lastPosition.intValue > 0) {
1501             vlc_value_t pos;
1502             var_Get(p_input_thread, "position", &pos);
1503             float f_current_pos = 100. * pos.f_float;
1504             long long int dur = input_item_GetDuration(p_item) / 1000000;
1505             int current_pos_in_sec = (f_current_pos * dur) / 100;
1506
1507             if (current_pos_in_sec >= lastPosition.intValue)
1508                 return;
1509
1510             int settingValue = config_GetInt(VLCIntf, "macosx-continue-playback");
1511             NSInteger returnValue = NSAlertErrorReturn;
1512
1513             if (settingValue == 0) {
1514                 NSAlert *theAlert = [NSAlert alertWithMessageText:_NS("Continue playback?") defaultButton:_NS("Continue") alternateButton:_NS("Restart playback") otherButton:_NS("Always continue") informativeTextWithFormat:_NS("Playback of \"%@\" will continue at %@"), [NSString stringWithUTF8String:input_item_GetTitleFbName(p_item)], [[VLCStringUtility sharedInstance] stringForTime:lastPosition.intValue]];
1515
1516                 [[VLCCoreInteraction sharedInstance] pause];
1517                 returnValue = [theAlert runModal];
1518                 [[VLCCoreInteraction sharedInstance] playOrPause];
1519             }
1520
1521             if (returnValue == NSAlertAlternateReturn || settingValue == 2)
1522                 lastPosition = [NSNumber numberWithInt:0];
1523
1524             pos.f_float = (float)lastPosition.intValue / (float)dur;
1525             msg_Dbg(VLCIntf, "continuing playback at %2.2f", pos.f_float);
1526             var_Set(p_input_thread, "position", pos);
1527
1528             if (returnValue == NSAlertOtherReturn)
1529                 config_PutInt(VLCIntf, "macosx-continue-playback", 1);
1530         }
1531     }
1532 }
1533
1534 - (void)storePlaybackPositionForItem:(input_thread_t *)p_input_thread
1535 {
1536     input_item_t *p_item = input_GetItem(p_input_thread);
1537     if (!p_item)
1538         return;
1539
1540     char *psz_url = decode_URI(input_item_GetURI(p_item));
1541     NSString *url = [NSString stringWithUTF8String:psz_url ? psz_url : ""];
1542     free(psz_url);
1543
1544     if (url.length < 1)
1545         return;
1546
1547     if ([url rangeOfString:@"file://" options:NSCaseInsensitiveSearch].location != 0)
1548         return;
1549
1550     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
1551     NSMutableDictionary *mutDict = [[NSMutableDictionary alloc] initWithDictionary:[defaults objectForKey:@"recentlyPlayedMedia"]];
1552
1553     vlc_value_t pos;
1554     var_Get(p_input_thread, "position", &pos);
1555     float f_current_pos = 100. * pos.f_float;
1556     long long int dur = input_item_GetDuration(p_item) / 1000000;
1557     int current_pos_in_sec = (f_current_pos * dur) / 100;
1558     NSMutableArray *mediaList = [[defaults objectForKey:@"recentlyPlayedMediaList"] mutableCopy];
1559
1560     if (pos.f_float > .05 && pos.f_float < .95 && dur > 180) {
1561         [mutDict setObject:[NSNumber numberWithInt:current_pos_in_sec] forKey:url];
1562
1563         [mediaList removeObject:url];
1564         [mediaList addObject:url];
1565         NSUInteger mediaListCount = mediaList.count;
1566         if (mediaListCount > 30) {
1567             for (NSUInteger x = 0; x < mediaListCount - 30; x++) {
1568                 [mutDict removeObjectForKey:[mediaList objectAtIndex:0]];
1569                 [mediaList removeObjectAtIndex:0];
1570             }
1571         }
1572     } else {
1573         [mutDict removeObjectForKey:url];
1574         [mediaList removeObject:url];
1575     }
1576     [defaults setObject:mutDict forKey:@"recentlyPlayedMedia"];
1577     [defaults setObject:mediaList forKey:@"recentlyPlayedMediaList"];
1578     [defaults synchronize];
1579
1580     [mutDict release];
1581     [mediaList release];
1582 }
1583
1584 @end
1585
1586 @implementation VLCPlaylist (NSOutlineViewDataSource)
1587
1588 - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item
1589 {
1590     id o_value = [super outlineView: outlineView child: index ofItem: item];
1591
1592     [o_outline_dict setObject:o_value forKey:[NSString stringWithFormat:@"%p", [o_value pointerValue]]];
1593     return o_value;
1594 }
1595
1596 /* Required for drag & drop and reordering */
1597 - (BOOL)outlineView:(NSOutlineView *)outlineView writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pboard
1598 {
1599     playlist_t *p_playlist = pl_Get(VLCIntf);
1600
1601     /* First remove the items that were moved during the last drag & drop
1602        operation */
1603     [o_items_array removeAllObjects];
1604     [o_nodes_array removeAllObjects];
1605
1606     NSUInteger itemCount = [items count];
1607
1608     for (NSUInteger i = 0 ; i < itemCount ; i++) {
1609         id o_item = [items objectAtIndex:i];
1610
1611         /* Fill the items and nodes to move in 2 different arrays */
1612         if (((playlist_item_t *)[o_item pointerValue])->i_children > 0)
1613             [o_nodes_array addObject: o_item];
1614         else
1615             [o_items_array addObject: o_item];
1616     }
1617
1618     /* Now we need to check if there are selected items that are in already
1619        selected nodes. In that case, we only want to move the nodes */
1620     [self removeItemsFrom: o_nodes_array ifChildrenOf: o_nodes_array];
1621     [self removeItemsFrom: o_items_array ifChildrenOf: o_nodes_array];
1622
1623     /* We add the "VLCPlaylistItemPboardType" type to be able to recognize
1624        a Drop operation coming from the playlist. */
1625
1626     [pboard declareTypes: [NSArray arrayWithObject:@"VLCPlaylistItemPboardType"] owner: self];
1627     [pboard setData:[NSData data] forType:@"VLCPlaylistItemPboardType"];
1628
1629     return YES;
1630 }
1631
1632 - (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id <NSDraggingInfo>)info proposedItem:(id)item proposedChildIndex:(NSInteger)index
1633 {
1634     playlist_t *p_playlist = pl_Get(VLCIntf);
1635     NSPasteboard *o_pasteboard = [info draggingPasteboard];
1636
1637     if (!p_playlist) return NSDragOperationNone;
1638
1639     /* Dropping ON items is not allowed if item is not a node */
1640     if (item) {
1641         if (index == NSOutlineViewDropOnItemIndex &&
1642                 ((playlist_item_t *)[item pointerValue])->i_children == -1) {
1643             return NSDragOperationNone;
1644         }
1645     }
1646
1647     /* We refuse to drop an item in anything else than a child of the General
1648        Node. We still accept items that would be root nodes of the outlineview
1649        however, to allow drop in an empty playlist. */
1650     if (!(([self isItem: [item pointerValue] inNode: p_playlist->p_local_category checkItemExistence: NO locked: NO] ||
1651             (var_CreateGetBool(p_playlist, "media-library") && [self isItem: [item pointerValue] inNode: p_playlist->p_ml_category checkItemExistence: NO locked: NO])) || item == nil)) {
1652         return NSDragOperationNone;
1653     }
1654
1655     /* Drop from the Playlist */
1656     if ([[o_pasteboard types] containsObject: @"VLCPlaylistItemPboardType"]) {
1657         NSUInteger count = [o_nodes_array count];
1658         for (NSUInteger i = 0 ; i < count ; i++) {
1659             /* We refuse to Drop in a child of an item we are moving */
1660             if ([self isItem: [item pointerValue] inNode: [[o_nodes_array objectAtIndex:i] pointerValue] checkItemExistence: NO locked:NO]) {
1661                 return NSDragOperationNone;
1662             }
1663         }
1664         return NSDragOperationMove;
1665     }
1666     /* Drop from the Finder */
1667     else if ([[o_pasteboard types] containsObject: NSFilenamesPboardType]) {
1668         return NSDragOperationGeneric;
1669     }
1670     return NSDragOperationNone;
1671 }
1672
1673 - (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id <NSDraggingInfo>)info item:(id)item childIndex:(NSInteger)index
1674 {
1675     playlist_t * p_playlist =  pl_Get(VLCIntf);
1676     NSPasteboard *o_pasteboard = [info draggingPasteboard];
1677
1678     /* Drag & Drop inside the playlist */
1679     if ([[o_pasteboard types] containsObject: @"VLCPlaylistItemPboardType"]) {
1680         if (index == -1) // this is no valid target, sanitize to top of table
1681             index = 0;
1682
1683         int i_row = 0;
1684         playlist_item_t *p_new_parent, *p_item = NULL;
1685         NSArray *o_all_items = [o_nodes_array arrayByAddingObjectsFromArray: o_items_array];
1686         /* If the item is to be dropped as root item of the outline, make it a
1687            child of the respective general node, if is either the pl or the ml
1688            Else, choose the proposed parent as parent. */
1689         if (item == nil) {
1690             if ([self currentPlaylistRoot] == p_playlist->p_local_category || [self currentPlaylistRoot] == p_playlist->p_ml_category)
1691                 p_new_parent = [self currentPlaylistRoot];
1692             else
1693                 return NO;
1694         }
1695         else
1696             p_new_parent = [item pointerValue];
1697
1698         /* Make sure the proposed parent is a node.
1699            (This should never be true) */
1700         if (p_new_parent->i_children < 0)
1701             return NO;
1702
1703         NSUInteger count = [o_all_items count];
1704         if (count == 0)
1705             return NO;
1706
1707         playlist_item_t **pp_items = (playlist_item_t **)calloc(count, sizeof(playlist_item_t*));
1708         if (!pp_items)
1709             return NO;
1710
1711         PL_LOCK;
1712         NSUInteger j = 0;
1713         for (NSUInteger i = 0; i < count; i++) {
1714             p_item = [[o_all_items objectAtIndex:i] pointerValue];
1715             if (p_item)
1716                 pp_items[j++] = p_item;
1717         }
1718
1719         if (j == 0 || playlist_TreeMoveMany(p_playlist, j, pp_items, p_new_parent, index) != VLC_SUCCESS) {
1720             PL_UNLOCK;
1721             free(pp_items);
1722             return NO;
1723         }
1724
1725         PL_UNLOCK;
1726         free(pp_items);
1727
1728         [self playlistUpdated];
1729         i_row = [o_outline_view rowForItem:[o_outline_dict objectForKey:[NSString stringWithFormat: @"%p", [[o_all_items objectAtIndex:0] pointerValue]]]];
1730
1731         if (i_row == -1)
1732             i_row = [o_outline_view rowForItem:[o_outline_dict objectForKey:[NSString stringWithFormat: @"%p", p_new_parent]]];
1733
1734         [o_outline_view deselectAll: self];
1735         [o_outline_view selectRowIndexes:[NSIndexSet indexSetWithIndex:i_row] byExtendingSelection:NO];
1736         [o_outline_view scrollRowToVisible: i_row];
1737
1738         return YES;
1739     }
1740
1741     else if ([[o_pasteboard types] containsObject: NSFilenamesPboardType]) {
1742         if ([self currentPlaylistRoot] != p_playlist->p_local_category && [self currentPlaylistRoot] != p_playlist->p_ml_category)
1743             return NO;
1744
1745         playlist_item_t *p_node = [item pointerValue];
1746
1747         NSArray *o_values = [[o_pasteboard propertyListForType: NSFilenamesPboardType]
1748                                 sortedArrayUsingSelector: @selector(caseInsensitiveCompare:)];
1749         NSUInteger count = [o_values count];
1750         NSMutableArray *o_array = [NSMutableArray arrayWithCapacity:count];
1751         input_thread_t * p_input = pl_CurrentInput(VLCIntf);
1752
1753         if (count == 1 && p_input) {
1754             int i_result = input_AddSubtitleOSD(p_input, vlc_path2uri([[o_values objectAtIndex:0] UTF8String], NULL), true, true);
1755             vlc_object_release(p_input);
1756             if (i_result == VLC_SUCCESS)
1757                 return YES;
1758         }
1759         else if (p_input)
1760             vlc_object_release(p_input);
1761
1762         for (NSUInteger i = 0; i < count; i++) {
1763             NSDictionary *o_dic;
1764             char *psz_uri = vlc_path2uri([[o_values objectAtIndex:i] UTF8String], NULL);
1765             if (!psz_uri)
1766                 continue;
1767
1768             o_dic = [NSDictionary dictionaryWithObject:[NSString stringWithCString:psz_uri encoding:NSUTF8StringEncoding] forKey:@"ITEM_URL"];
1769
1770             free(psz_uri);
1771
1772             [o_array addObject: o_dic];
1773         }
1774
1775         if (item == nil)
1776             [self appendArray:o_array atPos:index enqueue: YES];
1777         else {
1778             assert(p_node->i_children != -1);
1779             [self appendNodeArray:o_array inNode: p_node atPos:index enqueue:YES];
1780         }
1781         return YES;
1782     }
1783     return NO;
1784 }
1785
1786 @end