]> git.sesse.net Git - vlc/blob - modules/gui/macosx/playlist.m
macosx: don't continue playback where left off if user set a custom start-time
[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     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
485     NSMutableArray * o_columnArray = [[NSMutableArray alloc] init];
486     [o_columnArray addObject: [NSArray arrayWithObjects:TITLE_COLUMN, [NSNumber numberWithFloat:190.], nil]];
487     [o_columnArray addObject: [NSArray arrayWithObjects:ARTIST_COLUMN, [NSNumber numberWithFloat:95.], nil]];
488     [o_columnArray addObject: [NSArray arrayWithObjects:DURATION_COLUMN, [NSNumber numberWithFloat:95.], nil]];
489     NSDictionary *appDefaults = [NSDictionary dictionaryWithObject:[NSArray arrayWithArray:o_columnArray] forKey: @"PlaylistColumnSelection"];
490
491     [defaults registerDefaults:appDefaults];
492     [o_columnArray release];
493 }
494
495 - (id)init
496 {
497     self = [super init];
498     if (self != nil) {
499         o_nodes_array = [[NSMutableArray alloc] init];
500         o_items_array = [[NSMutableArray alloc] init];
501     }
502     return self;
503 }
504
505 - (void)dealloc
506 {
507     [o_nodes_array release];
508     [o_items_array release];
509     [super dealloc];
510 }
511
512 - (void)awakeFromNib
513 {
514     if (b_view_setup)
515         return;
516
517     playlist_t * p_playlist = pl_Get(VLCIntf);
518
519     [super awakeFromNib];
520     [self initStrings];
521
522     [o_outline_view setDoubleAction: @selector(playItem:)];
523     [o_outline_view_other setDoubleAction: @selector(playItem:)];
524
525     [o_outline_view registerForDraggedTypes: [NSArray arrayWithObjects:NSFilenamesPboardType, @"VLCPlaylistItemPboardType", nil]];
526     [o_outline_view setIntercellSpacing: NSMakeSize (0.0, 1.0)];
527
528     [o_outline_view_other registerForDraggedTypes: [NSArray arrayWithObjects:NSFilenamesPboardType, @"VLCPlaylistItemPboardType", nil]];
529     [o_outline_view_other setIntercellSpacing: NSMakeSize (0.0, 1.0)];
530
531     /* This uses a private API, but works fine on all current OSX releases.
532      * Radar ID 11739459 request a public API for this. However, it is probably
533      * easier and faster to recreate similar looking bitmaps ourselves. */
534     o_ascendingSortingImage = [[NSOutlineView class] _defaultTableHeaderSortImage];
535     o_descendingSortingImage = [[NSOutlineView class] _defaultTableHeaderReverseSortImage];
536
537     o_tc_sortColumn = nil;
538
539     NSArray * o_columnArray = [[NSUserDefaults standardUserDefaults] arrayForKey:@"PlaylistColumnSelection"];
540     NSUInteger count = [o_columnArray count];
541
542     id o_menu = [[VLCMain sharedInstance] mainMenu];
543     NSString * o_column;
544
545     NSMenu *o_context_menu = [o_menu setupPlaylistTableColumnsMenu];
546     [o_playlist_header setMenu: o_context_menu];
547
548     for (NSUInteger i = 0; i < count; i++) {
549         o_column = [[o_columnArray objectAtIndex:i] objectAtIndex:0];
550         if ([o_column isEqualToString:@"status"])
551             continue;
552
553         if(![o_menu setPlaylistColumnTableState: NSOnState forColumn: o_column])
554             continue;
555
556         [[o_outline_view tableColumnWithIdentifier: o_column] setWidth: [[[o_columnArray objectAtIndex:i] objectAtIndex:1] floatValue]];
557     }
558
559     [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(applicationWillTerminate:) name: NSApplicationWillTerminateNotification object: nil];
560
561     b_view_setup = YES;
562 }
563
564 - (void)applicationWillTerminate:(NSNotification *)notification
565 {
566     /* 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 */
567     [self saveTableColumns];
568 }
569
570 - (void)searchfieldChanged:(NSNotification *)o_notification
571 {
572     [o_search_field setStringValue:[[o_notification object] stringValue]];
573 }
574
575 - (void)initStrings
576 {
577     [o_mi_play setTitle: _NS("Play")];
578     [o_mi_delete setTitle: _NS("Delete")];
579     [o_mi_recursive_expand setTitle: _NS("Expand Node")];
580     [o_mi_selectall setTitle: _NS("Select All")];
581     [o_mi_info setTitle: _NS("Media Information...")];
582     [o_mi_dl_cover_art setTitle: _NS("Download Cover Art")];
583     [o_mi_preparse setTitle: _NS("Fetch Meta Data")];
584     [o_mi_revealInFinder setTitle: _NS("Reveal in Finder")];
585     [o_mm_mi_revealInFinder setTitle: _NS("Reveal in Finder")];
586     [[o_mm_mi_revealInFinder menu] setAutoenablesItems: NO];
587     [o_mi_sort_name setTitle: _NS("Sort Node by Name")];
588     [o_mi_sort_author setTitle: _NS("Sort Node by Author")];
589
590     [o_search_field setToolTip: _NS("Search in Playlist")];
591 }
592
593 - (void)playlistUpdated
594 {
595     /* Clear indications of any existing column sorting */
596     NSUInteger count = [[o_outline_view tableColumns] count];
597     for (NSUInteger i = 0 ; i < count ; i++)
598         [o_outline_view setIndicatorImage:nil inTableColumn: [[o_outline_view tableColumns] objectAtIndex:i]];
599
600     [o_outline_view setHighlightedTableColumn:nil];
601     o_tc_sortColumn = nil;
602     // TODO Find a way to keep the dict size to a minimum
603     //[o_outline_dict removeAllObjects];
604     [o_outline_view reloadData];
605     [[[[VLCMain sharedInstance] wizard] playlistWizard] reloadOutlineView];
606     [[[[VLCMain sharedInstance] bookmarks] dataTable] reloadData];
607
608     [o_outline_view selectRowIndexes:[NSIndexSet indexSetWithIndex:retainedRowSelection] byExtendingSelection:NO];
609
610     [self outlineViewSelectionDidChange: nil];
611     [[VLCMain sharedInstance] updateMainWindow];
612 }
613
614 - (void)outlineViewSelectionDidChange:(NSNotification *)notification
615 {
616     // FIXME: unsafe
617     playlist_item_t * p_item = [[o_outline_view itemAtRow:[o_outline_view selectedRow]] pointerValue];
618
619     if (p_item) {
620         /* update the state of our Reveal-in-Finder menu items */
621         NSMutableString *o_mrl;
622         char *psz_uri = input_item_GetURI(p_item->p_input);
623
624         [o_mi_revealInFinder setEnabled: NO];
625         [o_mm_mi_revealInFinder setEnabled: NO];
626         if (psz_uri) {
627             o_mrl = [NSMutableString stringWithUTF8String: psz_uri];
628
629             /* perform some checks whether it is a file and if it is local at all... */
630             NSRange prefix_range = [o_mrl rangeOfString: @"file:"];
631             if (prefix_range.location != NSNotFound)
632                 [o_mrl deleteCharactersInRange: prefix_range];
633
634             if ([o_mrl characterAtIndex:0] == '/') {
635                 [o_mi_revealInFinder setEnabled: YES];
636                 [o_mm_mi_revealInFinder setEnabled: YES];
637             }
638             free(psz_uri);
639         }
640
641         /* update our info-panel to reflect the new item */
642         [[[VLCMain sharedInstance] info] updatePanelWithItem:p_item->p_input];
643     }
644 }
645
646 - (BOOL)isSelectionEmpty
647 {
648     return [o_outline_view selectedRow] == -1;
649 }
650
651 - (void)updateRowSelection
652 {
653     // FIXME: unsafe
654     playlist_t *p_playlist = pl_Get(VLCIntf);
655     playlist_item_t *p_item, *p_temp_item;
656     NSMutableArray *o_array = [NSMutableArray array];
657
658     PL_LOCK;
659     p_item = playlist_CurrentPlayingItem(p_playlist);
660     if (p_item == NULL) {
661         PL_UNLOCK;
662         return;
663     }
664
665     p_temp_item = p_item;
666     while(p_temp_item->p_parent) {
667         [o_array insertObject: [NSValue valueWithPointer: p_temp_item] atIndex: 0];
668         p_temp_item = p_temp_item->p_parent;
669     }
670     PL_UNLOCK;
671
672     NSUInteger count = [o_array count];
673     for (NSUInteger j = 0; j < count - 1; j++) {
674         id o_item;
675         if ((o_item = [o_outline_dict objectForKey:
676                             [NSString stringWithFormat: @"%p",
677                             [[o_array objectAtIndex:j] pointerValue]]]) != nil) {
678             [o_outline_view expandItem: o_item];
679         }
680     }
681
682     id o_item = [o_outline_dict objectForKey:[NSString stringWithFormat: @"%p", p_item]];
683     NSInteger i_index = [o_outline_view rowForItem:o_item];
684     [o_outline_view selectRowIndexes:[NSIndexSet indexSetWithIndex:i_index] byExtendingSelection:NO];
685     [o_outline_view setNeedsDisplay:YES];
686 }
687
688 /* Check if p_item is a child of p_node recursively. We need to check the item
689    existence first since OSX sometimes tries to redraw items that have been
690    deleted. We don't do it when not required since this verification takes
691    quite a long time on big playlists (yes, pretty hacky). */
692
693 - (BOOL)isItem: (playlist_item_t *)p_item inNode: (playlist_item_t *)p_node checkItemExistence:(BOOL)b_check locked:(BOOL)b_locked
694 {
695     playlist_t * p_playlist = pl_Get(VLCIntf);
696     playlist_item_t *p_temp_item = p_item;
697
698     if (!p_node)
699         return NO;
700
701     if (p_node == p_item)
702         return YES;
703
704     if (p_node->i_children < 1)
705         return NO;
706
707     if (p_temp_item) {
708         int i;
709         if (!b_locked) PL_LOCK;
710
711         if (b_check) {
712         /* Since outlineView: willDisplayCell:... may call this function with
713            p_items that don't exist anymore, first check if the item is still
714            in the playlist. Any cleaner solution welcomed. */
715             for (i = 0; i < p_playlist->all_items.i_size; i++) {
716                 if (ARRAY_VAL(p_playlist->all_items, i) == p_item)
717                     break;
718                 else if (i == p_playlist->all_items.i_size - 1)
719                 {
720                     if (!b_locked) PL_UNLOCK;
721                     return NO;
722                 }
723             }
724         }
725
726         while(p_temp_item) {
727             p_temp_item = p_temp_item->p_parent;
728             if (p_temp_item == p_node) {
729                 if (!b_locked) PL_UNLOCK;
730                 return YES;
731             }
732         }
733         if (!b_locked) PL_UNLOCK;
734     }
735     return NO;
736 }
737
738 /* This method is useful for instance to remove the selected children of an
739    already selected node */
740 - (void)removeItemsFrom:(id)o_items ifChildrenOf:(id)o_nodes
741 {
742     NSUInteger itemCount = [o_items count];
743     NSUInteger nodeCount = [o_nodes count];
744     for (NSUInteger i = 0 ; i < itemCount ; i++) {
745         for (NSUInteger j = 0 ; j < nodeCount ; j++) {
746             if (o_items == o_nodes) {
747                 if (j == i) continue;
748             }
749             if ([self isItem: [[o_items objectAtIndex:i] pointerValue]
750                     inNode: [[o_nodes objectAtIndex:j] pointerValue]
751                     checkItemExistence: NO locked:NO]) {
752                 [o_items removeObjectAtIndex:i];
753                 /* We need to execute the next iteration with the same index
754                    since the current item has been deleted */
755                 i--;
756                 break;
757             }
758         }
759     }
760 }
761
762 - (IBAction)savePlaylist:(id)sender
763 {
764     playlist_t * p_playlist = pl_Get(VLCIntf);
765
766     NSSavePanel *o_save_panel = [NSSavePanel savePanel];
767     NSString * o_name = [NSString stringWithFormat: @"%@", _NS("Untitled")];
768
769     [NSBundle loadNibNamed:@"PlaylistAccessoryView" owner:self];
770
771     [o_save_accessory_text setStringValue: _NS("File Format:")];
772     [[o_save_accessory_popup itemAtIndex:0] setTitle: _NS("Extended M3U")];
773     [[o_save_accessory_popup itemAtIndex:1] setTitle: _NS("XML Shareable Playlist Format (XSPF)")];
774     [[o_save_accessory_popup itemAtIndex:2] setTitle: _NS("HTML playlist")];
775
776     [o_save_panel setTitle: _NS("Save Playlist")];
777     [o_save_panel setPrompt: _NS("Save")];
778     [o_save_panel setAccessoryView: o_save_accessory_view];
779     [o_save_panel setNameFieldStringValue: o_name];
780
781     if ([o_save_panel runModal] == NSFileHandlingPanelOKButton) {
782         NSString *o_filename = [[o_save_panel URL] path];
783
784         if ([o_save_accessory_popup indexOfSelectedItem] == 0) {
785             NSString * o_real_filename;
786             NSRange range;
787             range.location = [o_filename length] - [@".m3u" length];
788             range.length = [@".m3u" length];
789
790             if ([o_filename compare:@".m3u" options: NSCaseInsensitiveSearch range: range] != NSOrderedSame)
791                 o_real_filename = [NSString stringWithFormat: @"%@.m3u", o_filename];
792             else
793                 o_real_filename = o_filename;
794
795             playlist_Export(p_playlist,
796                 [o_real_filename fileSystemRepresentation],
797                 p_playlist->p_local_category, "export-m3u");
798         } else if ([o_save_accessory_popup indexOfSelectedItem] == 1) {
799             NSString * o_real_filename;
800             NSRange range;
801             range.location = [o_filename length] - [@".xspf" length];
802             range.length = [@".xspf" length];
803
804             if ([o_filename compare:@".xspf" options: NSCaseInsensitiveSearch range: range] != NSOrderedSame)
805                 o_real_filename = [NSString stringWithFormat: @"%@.xspf", o_filename];
806             else
807                 o_real_filename = o_filename;
808
809             playlist_Export(p_playlist,
810                 [o_real_filename fileSystemRepresentation],
811                 p_playlist->p_local_category, "export-xspf");
812         } else {
813             NSString * o_real_filename;
814             NSRange range;
815             range.location = [o_filename length] - [@".html" length];
816             range.length = [@".html" length];
817
818             if ([o_filename compare:@".html" options: NSCaseInsensitiveSearch range: range] != NSOrderedSame)
819                 o_real_filename = [NSString stringWithFormat: @"%@.html", o_filename];
820             else
821                 o_real_filename = o_filename;
822
823             playlist_Export(p_playlist,
824                 [o_real_filename fileSystemRepresentation],
825                 p_playlist->p_local_category, "export-html");
826         }
827     }
828 }
829
830 /* When called retrieves the selected outlineview row and plays that node or item */
831 - (IBAction)playItem:(id)sender
832 {
833     intf_thread_t * p_intf = VLCIntf;
834     playlist_t * p_playlist = pl_Get(p_intf);
835
836     playlist_item_t *p_item;
837     playlist_item_t *p_node = NULL;
838
839     // ignore clicks on column header when handling double action
840     if (sender != nil && [o_outline_view clickedRow] == -1 && sender != o_mi_play)
841         return;
842
843     p_item = [[o_outline_view itemAtRow:[o_outline_view selectedRow]] pointerValue];
844
845     PL_LOCK;
846     if (p_item) {
847         if (p_item->i_children == -1) {
848             p_node = p_item->p_parent;
849         } else {
850             p_node = p_item;
851             if (p_node->i_children > 0 && p_node->pp_children[0]->i_children == -1)
852                 p_item = p_node->pp_children[0];
853             else
854                 p_item = NULL;
855         }
856
857         playlist_Control(p_playlist, PLAYLIST_VIEWPLAY, pl_Locked, p_node, p_item);
858     }
859     PL_UNLOCK;
860 }
861
862 - (IBAction)revealItemInFinder:(id)sender
863 {
864     NSIndexSet * selectedRows = [o_outline_view selectedRowIndexes];
865     NSUInteger count = [selectedRows count];
866     NSUInteger indexes[count];
867     [selectedRows getIndexes:indexes maxCount:count inIndexRange:nil];
868
869     NSMutableString * o_mrl;
870     playlist_item_t *p_item;
871     for (NSUInteger i = 0; i < count; i++) {
872         p_item = [[o_outline_view itemAtRow:indexes[i]] pointerValue];
873
874         if (! p_item || !p_item->p_input)
875             continue;
876
877         char * psz_url = decode_URI(input_item_GetURI(p_item->p_input));
878         o_mrl = [[NSMutableString alloc] initWithString: [NSString stringWithUTF8String:psz_url ? psz_url : ""]];
879         if (psz_url != NULL)
880             free( psz_url );
881
882         /* perform some checks whether it is a file and if it is local at all... */
883         if ([o_mrl length] > 0) {
884             NSRange prefix_range = [o_mrl rangeOfString: @"file:"];
885             if (prefix_range.location != NSNotFound)
886                 [o_mrl deleteCharactersInRange: prefix_range];
887
888             if ([o_mrl characterAtIndex:0] == '/')
889                 [[NSWorkspace sharedWorkspace] selectFile: o_mrl inFileViewerRootedAtPath: o_mrl];
890         }
891
892         [o_mrl release];
893     }
894 }
895
896 /* When called retrieves the selected outlineview row and plays that node or item */
897 - (IBAction)preparseItem:(id)sender
898 {
899     int i_count;
900     NSIndexSet *o_selected_indexes;
901     intf_thread_t * p_intf = VLCIntf;
902     playlist_t * p_playlist = pl_Get(p_intf);
903     playlist_item_t *p_item = NULL;
904
905     o_selected_indexes = [o_outline_view selectedRowIndexes];
906     i_count = [o_selected_indexes count];
907
908     NSUInteger indexes[i_count];
909     [o_selected_indexes getIndexes:indexes maxCount:i_count inIndexRange:nil];
910     for (int i = 0; i < i_count; i++) {
911         p_item = [[o_outline_view itemAtRow:indexes[i]] pointerValue];
912         [o_outline_view deselectRow: indexes[i]];
913
914         if (p_item) {
915             if (p_item->i_children == -1)
916                 libvlc_MetaRequest(p_intf->p_libvlc, p_item->p_input);
917             else
918                 msg_Dbg(p_intf, "preparsing nodes not implemented");
919         }
920     }
921     [self playlistUpdated];
922 }
923
924 - (IBAction)downloadCoverArt:(id)sender
925 {
926     int i_count;
927     NSIndexSet *o_selected_indexes;
928     intf_thread_t * p_intf = VLCIntf;
929     playlist_t * p_playlist = pl_Get(p_intf);
930     playlist_item_t *p_item = NULL;
931
932     o_selected_indexes = [o_outline_view selectedRowIndexes];
933     i_count = [o_selected_indexes count];
934
935     NSUInteger indexes[i_count];
936     [o_selected_indexes getIndexes:indexes maxCount:i_count inIndexRange:nil];
937     for (int i = 0; i < i_count; i++) {
938         p_item = [[o_outline_view itemAtRow: indexes[i]] pointerValue];
939
940         if (p_item && p_item->i_children == -1)
941             libvlc_ArtRequest(p_intf->p_libvlc, p_item->p_input);
942     }
943     [self playlistUpdated];
944 }
945
946 - (IBAction)selectAll:(id)sender
947 {
948     [o_outline_view selectAll: nil];
949 }
950
951 - (IBAction)showInfoPanel:(id)sender
952 {
953     [[[VLCMain sharedInstance] info] initPanel];
954 }
955
956 - (IBAction)deleteItem:(id)sender
957 {
958     int i_count;
959     NSIndexSet *o_selected_indexes;
960     playlist_t * p_playlist;
961     intf_thread_t * p_intf = VLCIntf;
962
963     o_selected_indexes = [o_outline_view selectedRowIndexes];
964     i_count = [o_selected_indexes count];
965     retainedRowSelection = [o_selected_indexes firstIndex];
966
967     p_playlist = pl_Get(p_intf);
968
969     NSUInteger indexes[i_count];
970     if (i_count == [o_outline_view numberOfRows]) {
971         PL_LOCK;
972         playlist_NodeDelete(p_playlist, [self currentPlaylistRoot], true, false);
973         PL_UNLOCK;
974         [self playlistUpdated];
975         return;
976     }
977     [o_selected_indexes getIndexes:indexes maxCount:i_count inIndexRange:nil];
978     for (int i = 0; i < i_count; i++) {
979         id o_item = [o_outline_view itemAtRow: indexes[i]];
980         [o_outline_view deselectRow: indexes[i]];
981
982         PL_LOCK;
983         playlist_item_t *p_item = [o_item pointerValue];
984         if (!p_item || !p_item->p_input) {
985             PL_UNLOCK;
986             continue;
987         }
988
989         if (p_item->i_children != -1) {
990         //is a node and not an item
991             if (playlist_Status(p_playlist) != PLAYLIST_STOPPED &&
992                 [self isItem: playlist_CurrentPlayingItem(p_playlist) inNode: ((playlist_item_t *)[o_item pointerValue])
993                         checkItemExistence: NO locked:YES] == YES)
994                 // if current item is in selected node and is playing then stop playlist
995                 playlist_Control(p_playlist, PLAYLIST_STOP, pl_Locked);
996
997                 playlist_NodeDelete(p_playlist, p_item, true, false);
998         } else
999             playlist_DeleteFromInput(p_playlist, p_item->p_input, pl_Locked);
1000
1001         PL_UNLOCK;
1002         [o_outline_dict removeObjectForKey:[NSString stringWithFormat:@"%p", [o_item pointerValue]]];
1003         [o_item release];
1004     }
1005
1006     [self playlistUpdated];
1007 }
1008
1009 - (IBAction)sortNodeByName:(id)sender
1010 {
1011     [self sortNode: SORT_TITLE];
1012 }
1013
1014 - (IBAction)sortNodeByAuthor:(id)sender
1015 {
1016     [self sortNode: SORT_ARTIST];
1017 }
1018
1019 - (void)sortNode:(int)i_mode
1020 {
1021     playlist_t * p_playlist = pl_Get(VLCIntf);
1022     playlist_item_t * p_item;
1023
1024     if ([o_outline_view selectedRow] > -1) {
1025         p_item = [[o_outline_view itemAtRow: [o_outline_view selectedRow]] pointerValue];
1026         if (!p_item)
1027             return;
1028     } else
1029         p_item = [self currentPlaylistRoot]; // If no item is selected, sort the whole playlist
1030
1031     PL_LOCK;
1032     if (p_item->i_children > -1) // the item is a node
1033         playlist_RecursiveNodeSort(p_playlist, p_item, i_mode, ORDER_NORMAL);
1034     else
1035         playlist_RecursiveNodeSort(p_playlist, p_item->p_parent, i_mode, ORDER_NORMAL);
1036
1037     PL_UNLOCK;
1038     [self playlistUpdated];
1039 }
1040
1041 - (input_item_t *)createItem:(NSDictionary *)o_one_item
1042 {
1043     intf_thread_t * p_intf = VLCIntf;
1044     playlist_t * p_playlist = pl_Get(p_intf);
1045
1046     input_item_t *p_input;
1047     BOOL b_rem = FALSE, b_dir = FALSE, b_writable = FALSE;
1048     NSString *o_uri, *o_name, *o_path;
1049     NSURL * o_nsurl;
1050     NSArray *o_options;
1051     NSURL *o_true_file;
1052
1053     /* Get the item */
1054     o_uri = (NSString *)[o_one_item objectForKey: @"ITEM_URL"];
1055     o_nsurl = [NSURL URLWithString: o_uri];
1056     o_path = [o_nsurl path];
1057     o_name = (NSString *)[o_one_item objectForKey: @"ITEM_NAME"];
1058     o_options = (NSArray *)[o_one_item objectForKey: @"ITEM_OPTIONS"];
1059
1060     if ([[NSFileManager defaultManager] fileExistsAtPath:o_path isDirectory:&b_dir] && b_dir &&
1061         [[NSWorkspace sharedWorkspace] getFileSystemInfoForPath:o_path isRemovable: &b_rem
1062                                                      isWritable:&b_writable isUnmountable:NULL description:NULL type:NULL] && b_rem && !b_writable && [o_nsurl isFileURL]) {
1063         id o_vlc_open = [[VLCMain sharedInstance] open];
1064
1065         NSString *diskType = [o_vlc_open getVolumeTypeFromMountPath: o_path];
1066         msg_Dbg(p_intf, "detected optical media of type %s in the file input", [diskType UTF8String]);
1067
1068         if ([diskType isEqualToString: kVLCMediaDVD])
1069             o_uri = [NSString stringWithFormat: @"dvdnav://%@", [o_vlc_open getBSDNodeFromMountPath: o_path]];
1070         else if ([diskType isEqualToString: kVLCMediaVideoTSFolder])
1071             o_uri = [NSString stringWithFormat: @"dvdnav://%@", o_path];
1072         else if ([diskType isEqualToString: kVLCMediaAudioCD])
1073             o_uri = [NSString stringWithFormat: @"cdda://%@", [o_vlc_open getBSDNodeFromMountPath: o_path]];
1074         else if ([diskType isEqualToString: kVLCMediaVCD])
1075             o_uri = [NSString stringWithFormat: @"vcd://%@#0:0", [o_vlc_open getBSDNodeFromMountPath: o_path]];
1076         else if ([diskType isEqualToString: kVLCMediaSVCD])
1077             o_uri = [NSString stringWithFormat: @"vcd://%@@0:0", [o_vlc_open getBSDNodeFromMountPath: o_path]];
1078         else if ([diskType isEqualToString: kVLCMediaBD] || [diskType isEqualToString: kVLCMediaBDMVFolder])
1079             o_uri = [NSString stringWithFormat: @"bluray://%@", o_path];
1080         else
1081             msg_Warn(VLCIntf, "unknown disk type, treating %s as regular input", [o_path UTF8String]);
1082
1083         p_input = input_item_New([o_uri UTF8String], [[[NSFileManager defaultManager] displayNameAtPath: o_path] UTF8String]);
1084     }
1085     else
1086         p_input = input_item_New([o_uri fileSystemRepresentation], o_name ? [o_name UTF8String] : NULL);
1087
1088     if (!p_input)
1089         return NULL;
1090
1091     if (o_options) {
1092         NSUInteger count = [o_options count];
1093         for (NSUInteger i = 0; i < count; i++)
1094             input_item_AddOption(p_input, [[o_options objectAtIndex:i] UTF8String], VLC_INPUT_OPTION_TRUSTED);
1095     }
1096
1097     /* Recent documents menu */
1098     if (o_nsurl != nil && (BOOL)config_GetInt(p_playlist, "macosx-recentitems") == YES)
1099         [[NSDocumentController sharedDocumentController] noteNewRecentDocumentURL: o_nsurl];
1100
1101     return p_input;
1102 }
1103
1104 - (void)appendArray:(NSArray*)o_array atPos:(int)i_position enqueue:(BOOL)b_enqueue
1105 {
1106     playlist_t * p_playlist = pl_Get(VLCIntf);
1107     NSUInteger count = [o_array count];
1108     BOOL b_usingPlaylist;
1109     if ([self currentPlaylistRoot] == p_playlist->p_ml_category)
1110         b_usingPlaylist = NO;
1111     else
1112         b_usingPlaylist = YES;
1113
1114     PL_LOCK;
1115     for (NSUInteger i_item = 0; i_item < count; i_item++) {
1116         input_item_t *p_input;
1117         NSDictionary *o_one_item;
1118
1119         /* Get the item */
1120         o_one_item = [o_array objectAtIndex:i_item];
1121         p_input = [self createItem: o_one_item];
1122         if (!p_input)
1123             continue;
1124
1125         /* Add the item */
1126         int returnValue = playlist_AddInput(p_playlist, p_input, PLAYLIST_INSERT, i_position == -1 ? PLAYLIST_END : i_position + i_item, b_usingPlaylist, pl_Locked);
1127         if (returnValue != VLC_SUCCESS) {
1128             vlc_gc_decref(p_input);
1129             continue;
1130         }
1131
1132         if (i_item == 0 && !b_enqueue) {
1133             playlist_item_t *p_item = playlist_ItemGetByInput(p_playlist, p_input);
1134             playlist_Control(p_playlist, PLAYLIST_VIEWPLAY, pl_Locked, p_item->p_parent, p_item);
1135         }
1136
1137         vlc_gc_decref(p_input);
1138     }
1139     PL_UNLOCK;
1140     [self playlistUpdated];
1141 }
1142
1143 - (void)appendNodeArray:(NSArray*)o_array inNode:(playlist_item_t *)p_node atPos:(int)i_position enqueue:(BOOL)b_enqueue
1144 {
1145     playlist_t * p_playlist = pl_Get(VLCIntf);
1146     NSUInteger count = [o_array count];
1147
1148     for (NSUInteger i_item = 0; i_item < count; i_item++) {
1149         input_item_t *p_input;
1150         NSDictionary *o_one_item;
1151
1152         /* Get the item */
1153         PL_LOCK;
1154         o_one_item = [o_array objectAtIndex:i_item];
1155         p_input = [self createItem: o_one_item];
1156
1157         if (!p_input)
1158             continue;
1159
1160         /* Add the item */
1161         playlist_NodeAddInput(p_playlist, p_input, p_node,
1162                                       PLAYLIST_INSERT,
1163                                       i_position == -1 ?
1164                                       PLAYLIST_END : i_position + i_item,
1165                                       pl_Locked);
1166
1167
1168         if (i_item == 0 && !b_enqueue) {
1169             playlist_item_t *p_item;
1170             p_item = playlist_ItemGetByInput(p_playlist, p_input);
1171             playlist_Control(p_playlist, PLAYLIST_VIEWPLAY, pl_Locked, p_node, p_item);
1172         }
1173         PL_UNLOCK;
1174         vlc_gc_decref(p_input);
1175     }
1176     [self playlistUpdated];
1177 }
1178
1179 - (NSMutableArray *)subSearchItem:(playlist_item_t *)p_item
1180 {
1181     playlist_t *p_playlist = pl_Get(VLCIntf);
1182     playlist_item_t *p_selected_item;
1183     int i_selected_row;
1184
1185     i_selected_row = [o_outline_view selectedRow];
1186     if (i_selected_row < 0)
1187         i_selected_row = 0;
1188
1189     p_selected_item = (playlist_item_t *)[[o_outline_view itemAtRow: i_selected_row] pointerValue];
1190
1191     for (NSUInteger i_current = 0; i_current < p_item->i_children ; i_current++) {
1192         char *psz_temp;
1193         NSString *o_current_name, *o_current_author;
1194
1195         PL_LOCK;
1196         o_current_name = [NSString stringWithUTF8String:p_item->pp_children[i_current]->p_input->psz_name];
1197         psz_temp = input_item_GetInfo(p_item->p_input, _("Meta-information"),_("Artist"));
1198         o_current_author = [NSString stringWithUTF8String:psz_temp];
1199         free(psz_temp);
1200         PL_UNLOCK;
1201
1202         if (p_selected_item == p_item->pp_children[i_current] && b_selected_item_met == NO)
1203             b_selected_item_met = YES;
1204         else if (p_selected_item == p_item->pp_children[i_current] && b_selected_item_met == YES)
1205             return NULL;
1206         else if (b_selected_item_met == YES &&
1207                     ([o_current_name rangeOfString:[o_search_field
1208                         stringValue] options:NSCaseInsensitiveSearch].length ||
1209                       [o_current_author rangeOfString:[o_search_field
1210                         stringValue] options:NSCaseInsensitiveSearch].length))
1211             /*Adds the parent items in the result array as well, so that we can
1212             expand the tree*/
1213             return [NSMutableArray arrayWithObject: [NSValue valueWithPointer: p_item->pp_children[i_current]]];
1214
1215         if (p_item->pp_children[i_current]->i_children > 0) {
1216             id o_result = [self subSearchItem:
1217                                             p_item->pp_children[i_current]];
1218             if (o_result != NULL) {
1219                 [o_result insertObject: [NSValue valueWithPointer:
1220                                 p_item->pp_children[i_current]] atIndex:0];
1221                 return o_result;
1222             }
1223         }
1224     }
1225     return NULL;
1226 }
1227
1228 - (IBAction)searchItem:(id)sender
1229 {
1230     playlist_t * p_playlist = pl_Get(VLCIntf);
1231     id o_result;
1232
1233     int i_row = -1;
1234
1235     b_selected_item_met = NO;
1236
1237     /* First, only search after the selected item:
1238      * (b_selected_item_met = NO) */
1239     o_result = [self subSearchItem:[self currentPlaylistRoot]];
1240     if (o_result == NULL)
1241         /* If the first search failed, search again from the beginning */
1242         o_result = [self subSearchItem:[self currentPlaylistRoot]];
1243
1244     if (o_result != NULL) {
1245         int i_start;
1246         if ([[o_result objectAtIndex:0] pointerValue] == p_playlist->p_local_category)
1247             i_start = 1;
1248         else
1249             i_start = 0;
1250         NSUInteger count = [o_result count];
1251
1252         for (NSUInteger i = i_start ; i < count - 1 ; i++) {
1253             [o_outline_view expandItem: [o_outline_dict objectForKey:
1254                         [NSString stringWithFormat: @"%p",
1255                         [[o_result objectAtIndex:i] pointerValue]]]];
1256         }
1257         i_row = [o_outline_view rowForItem: [o_outline_dict objectForKey:
1258                         [NSString stringWithFormat: @"%p",
1259                         [[o_result objectAtIndex:count - 1 ]
1260                         pointerValue]]]];
1261     }
1262     if (i_row > -1) {
1263         [o_outline_view selectRowIndexes:[NSIndexSet indexSetWithIndex:i_row] byExtendingSelection:NO];
1264         [o_outline_view scrollRowToVisible: i_row];
1265     }
1266 }
1267
1268 - (IBAction)recursiveExpandNode:(id)sender
1269 {
1270     NSIndexSet * selectedRows = [o_outline_view selectedRowIndexes];
1271     NSUInteger count = [selectedRows count];
1272     NSUInteger indexes[count];
1273     [selectedRows getIndexes:indexes maxCount:count inIndexRange:nil];
1274
1275     id o_item;
1276     playlist_item_t *p_item;
1277     for (NSUInteger i = 0; i < count; i++) {
1278         o_item = [o_outline_view itemAtRow: indexes[i]];
1279         p_item = (playlist_item_t *)[o_item pointerValue];
1280
1281         if (![[o_outline_view dataSource] outlineView: o_outline_view isItemExpandable: o_item])
1282             o_item = [o_outline_dict objectForKey: [NSString stringWithFormat: @"%p", p_item->p_parent]];
1283
1284         /* We need to collapse the node first, since OSX refuses to recursively
1285          expand an already expanded node, even if children nodes are collapsed. */
1286         [o_outline_view collapseItem: o_item collapseChildren: YES];
1287         [o_outline_view expandItem: o_item expandChildren: YES];
1288
1289         selectedRows = [o_outline_view selectedRowIndexes];
1290         [selectedRows getIndexes:indexes maxCount:count inIndexRange:nil];
1291     }
1292 }
1293
1294 - (NSMenu *)menuForEvent:(NSEvent *)o_event
1295 {
1296     if (!b_playlistmenu_nib_loaded)
1297         b_playlistmenu_nib_loaded = [NSBundle loadNibNamed:@"PlaylistMenu" owner:self];
1298
1299     NSPoint pt;
1300     bool b_rows;
1301     bool b_item_sel;
1302
1303     pt = [o_outline_view convertPoint: [o_event locationInWindow] fromView: nil];
1304     int row = [o_outline_view rowAtPoint:pt];
1305     if (row != -1 && ![[o_outline_view selectedRowIndexes] containsIndex: row])
1306         [o_outline_view selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO];
1307
1308     b_item_sel = (row != -1 && [o_outline_view selectedRow] != -1);
1309     b_rows = [o_outline_view numberOfRows] != 0;
1310
1311     [o_mi_play setEnabled: b_item_sel];
1312     [o_mi_delete setEnabled: b_item_sel];
1313     [o_mi_selectall setEnabled: b_rows];
1314     [o_mi_info setEnabled: b_item_sel];
1315     [o_mi_preparse setEnabled: b_item_sel];
1316     [o_mi_recursive_expand setEnabled: b_item_sel];
1317     [o_mi_sort_name setEnabled: b_item_sel];
1318     [o_mi_sort_author setEnabled: b_item_sel];
1319
1320     return o_ctx_menu;
1321 }
1322
1323 - (void)outlineView: (NSOutlineView *)o_tv didClickTableColumn:(NSTableColumn *)o_tc
1324 {
1325     int i_mode, i_type = 0;
1326     intf_thread_t *p_intf = VLCIntf;
1327     NSString * o_identifier = [o_tc identifier];
1328
1329     playlist_t *p_playlist = pl_Get(p_intf);
1330
1331     if ([o_identifier isEqualToString:TRACKNUM_COLUMN])
1332         i_mode = SORT_TRACK_NUMBER;
1333     else if ([o_identifier isEqualToString:TITLE_COLUMN])
1334         i_mode = SORT_TITLE;
1335     else if ([o_identifier isEqualToString:ARTIST_COLUMN])
1336         i_mode = SORT_ARTIST;
1337     else if ([o_identifier isEqualToString:GENRE_COLUMN])
1338         i_mode = SORT_GENRE;
1339     else if ([o_identifier isEqualToString:DURATION_COLUMN])
1340         i_mode = SORT_DURATION;
1341     else if ([o_identifier isEqualToString:ALBUM_COLUMN])
1342         i_mode = SORT_ALBUM;
1343     else if ([o_identifier isEqualToString:DESCRIPTION_COLUMN])
1344         i_mode = SORT_DESCRIPTION;
1345     else if ([o_identifier isEqualToString:URI_COLUMN])
1346         i_mode = SORT_URI;
1347     else
1348         return;
1349
1350     if (o_tc_sortColumn == o_tc)
1351         b_isSortDescending = !b_isSortDescending;
1352     else
1353         b_isSortDescending = false;
1354
1355     if (b_isSortDescending)
1356         i_type = ORDER_REVERSE;
1357     else
1358         i_type = ORDER_NORMAL;
1359
1360     PL_LOCK;
1361     playlist_RecursiveNodeSort(p_playlist, [self currentPlaylistRoot], i_mode, i_type);
1362     PL_UNLOCK;
1363
1364     [self playlistUpdated];
1365
1366     o_tc_sortColumn = o_tc;
1367     [o_outline_view setHighlightedTableColumn:o_tc];
1368
1369     if (b_isSortDescending)
1370         [o_outline_view setIndicatorImage:o_descendingSortingImage inTableColumn:o_tc];
1371     else
1372         [o_outline_view setIndicatorImage:o_ascendingSortingImage inTableColumn:o_tc];
1373 }
1374
1375
1376 - (void)outlineView:(NSOutlineView *)outlineView
1377                                 willDisplayCell:(id)cell
1378                                 forTableColumn:(NSTableColumn *)tableColumn
1379                                 item:(id)item
1380 {
1381     /* this method can be called when VLC is already dead, hence the extra checks */
1382     intf_thread_t * p_intf = VLCIntf;
1383     if (!p_intf)
1384         return;
1385     playlist_t *p_playlist = pl_Get(p_intf);
1386     if (!p_playlist)
1387         return;
1388
1389     id o_playing_item;
1390
1391     PL_LOCK;
1392     o_playing_item = [o_outline_dict objectForKey: [NSString stringWithFormat:@"%p",  playlist_CurrentPlayingItem(p_playlist)]];
1393     PL_UNLOCK;
1394
1395     NSFont *fontToUse;
1396     if (config_GetInt(VLCIntf, "macosx-large-text"))
1397         fontToUse = [NSFont systemFontOfSize:13.];
1398     else
1399         fontToUse = [NSFont systemFontOfSize:11.];
1400
1401     if ([self isItem: [o_playing_item pointerValue] inNode: [item pointerValue] checkItemExistence:YES locked:NO]
1402                         || [o_playing_item isEqual: item])
1403         [cell setFont: [[NSFontManager sharedFontManager] convertFont:fontToUse toHaveTrait:NSBoldFontMask]];
1404     else
1405         [cell setFont: [[NSFontManager sharedFontManager] convertFont:fontToUse toNotHaveTrait:NSBoldFontMask]];
1406 }
1407
1408 - (id)playingItem
1409 {
1410     playlist_t *p_playlist = pl_Get(VLCIntf);
1411
1412     id o_playing_item;
1413
1414     PL_LOCK;
1415     o_playing_item = [o_outline_dict objectForKey: [NSString stringWithFormat:@"%p",  playlist_CurrentPlayingItem(p_playlist)]];
1416     PL_UNLOCK;
1417
1418     return o_playing_item;
1419 }
1420
1421 - (NSArray *)draggedItems
1422 {
1423     return [[o_nodes_array arrayByAddingObjectsFromArray: o_items_array] retain];
1424 }
1425
1426 - (void)setColumn: (NSString *)o_column state: (NSInteger)i_state translationDict:(NSDictionary *)o_dict
1427 {
1428     NSTableColumn * o_work_tc;
1429
1430     if (i_state == NSOnState) {
1431         NSString *o_title = [o_dict objectForKey:o_column];
1432         if (!o_title)
1433             return;
1434
1435         o_work_tc = [[NSTableColumn alloc] initWithIdentifier: o_column];
1436         [o_work_tc setEditable: NO];
1437         [[o_work_tc dataCell] setFont: [NSFont controlContentFontOfSize:11.]];
1438
1439         [[o_work_tc headerCell] setStringValue: [o_dict objectForKey:o_column]];
1440
1441         if ([o_column isEqualToString: TRACKNUM_COLUMN]) {
1442             [o_work_tc setWidth: 20.];
1443             [o_work_tc setResizingMask: NSTableColumnNoResizing];
1444             [[o_work_tc headerCell] setStringValue: @"#"];
1445         }
1446
1447         [o_outline_view addTableColumn: o_work_tc];
1448         [o_work_tc release];
1449         [o_outline_view reloadData];
1450         [o_outline_view setNeedsDisplay: YES];
1451     }
1452     else
1453         [o_outline_view removeTableColumn: [o_outline_view tableColumnWithIdentifier: o_column]];
1454
1455     [o_outline_view setOutlineTableColumn: [o_outline_view tableColumnWithIdentifier:TITLE_COLUMN]];
1456 }
1457
1458 - (void)saveTableColumns
1459 {
1460     NSMutableArray * o_arrayToSave = [[NSMutableArray alloc] init];
1461     NSArray * o_columns = [[NSArray alloc] initWithArray:[o_outline_view tableColumns]];
1462     NSUInteger count = [o_columns count];
1463     NSTableColumn * o_currentColumn;
1464     for (NSUInteger i = 0; i < count; i++) {
1465         o_currentColumn = [o_columns objectAtIndex:i];
1466         [o_arrayToSave addObject:[NSArray arrayWithObjects:[o_currentColumn identifier], [NSNumber numberWithFloat:[o_currentColumn width]], nil]];
1467     }
1468     [[NSUserDefaults standardUserDefaults] setObject: o_arrayToSave forKey:@"PlaylistColumnSelection"];
1469     [[NSUserDefaults standardUserDefaults] synchronize];
1470     [o_columns release];
1471     [o_arrayToSave release];
1472 }
1473
1474 - (void)continuePlaybackWhereYouLeftOff:(input_thread_t *)p_input_thread
1475 {
1476     NSDictionary *recentlyPlayedFiles = [[NSUserDefaults standardUserDefaults] objectForKey:@"recentlyPlayedMedia"];
1477     if (recentlyPlayedFiles) {
1478         input_item_t *p_item = input_GetItem(p_input_thread);
1479         if (!p_item)
1480             return;
1481
1482         /* allow the user to over-write the start-time */
1483         if (p_item->i_options > 0) {
1484             for (int x = 0; x < p_item->i_options; x++) {
1485                 if (strstr(p_item->ppsz_options[x],"start-time"))
1486                     return;
1487             }
1488         }
1489
1490         char *psz_url = decode_URI(input_item_GetURI(p_item));
1491         NSString *url = [NSString stringWithUTF8String:psz_url ? psz_url : ""];
1492         free(psz_url);
1493
1494         NSNumber *lastPosition = [recentlyPlayedFiles objectForKey:url];
1495         if (lastPosition && lastPosition.intValue > 0) {
1496             vlc_value_t pos;
1497             var_Get(p_input_thread, "position", &pos);
1498             float f_current_pos = 100. * pos.f_float;
1499             long long int dur = input_item_GetDuration(p_item) / 1000000;
1500             int current_pos_in_sec = (f_current_pos * dur) / 100;
1501
1502             if (current_pos_in_sec >= lastPosition.intValue)
1503                 return;
1504
1505             int settingValue = config_GetInt(VLCIntf, "macosx-continue-playback");
1506             NSInteger returnValue = NSAlertErrorReturn;
1507
1508             if (settingValue == 0) {
1509                 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]];
1510
1511                 [[VLCCoreInteraction sharedInstance] pause];
1512                 returnValue = [theAlert runModal];
1513                 [[VLCCoreInteraction sharedInstance] playOrPause];
1514             }
1515
1516             if (returnValue == NSAlertAlternateReturn || settingValue == 2)
1517                 lastPosition = [NSNumber numberWithInt:0];
1518
1519             pos.f_float = (float)lastPosition.intValue / (float)dur;
1520             msg_Dbg(VLCIntf, "continuing playback at %2.2f", pos.f_float);
1521             var_Set(p_input_thread, "position", pos);
1522
1523             if (returnValue == NSAlertOtherReturn)
1524                 config_PutInt(VLCIntf, "macosx-continue-playback", 1);
1525         }
1526     }
1527 }
1528
1529 - (void)storePlaybackPositionForItem:(input_thread_t *)p_input_thread
1530 {
1531     input_item_t *p_item = input_GetItem(p_input_thread);
1532     if (!p_item)
1533         return;
1534
1535     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
1536     NSMutableDictionary *mutDict = [[NSMutableDictionary alloc] initWithDictionary:[defaults objectForKey:@"recentlyPlayedMedia"]];
1537
1538     char *psz_url = decode_URI(input_item_GetURI(p_item));
1539     NSString *url = [NSString stringWithUTF8String:psz_url ? psz_url : ""];
1540     free(psz_url);
1541     vlc_value_t pos;
1542     var_Get(p_input_thread, "position", &pos);
1543     float f_current_pos = 100. * pos.f_float;
1544     long long int dur = input_item_GetDuration(p_item) / 1000000;
1545     int current_pos_in_sec = (f_current_pos * dur) / 100;
1546     NSMutableArray *mediaList = [defaults objectForKey:@"recentlyPlayedMediaList"];
1547
1548     if (pos.f_float > .05 && pos.f_float < .95 && dur > 180) {
1549         [mutDict setObject:[NSNumber numberWithInt:current_pos_in_sec] forKey:url];
1550
1551         [mediaList removeObject:url];
1552         [mediaList addObject:url];
1553         NSUInteger mediaListCount = mediaList.count;
1554         if (mediaListCount > 30) {
1555             for (NSUInteger x = 0; x < mediaListCount - 30; x++) {
1556                 [mutDict removeObjectForKey:[mediaList objectAtIndex:0]];
1557                 [mediaList removeObjectAtIndex:0];
1558             }
1559         }
1560     } else {
1561         [mutDict removeObjectForKey:url];
1562         [mediaList removeObject:url];
1563     }
1564     [defaults setObject:mutDict forKey:@"recentlyPlayedMedia"];
1565     [defaults setObject:mediaList forKey:@"recentlyPlayedMediaList"];
1566     [defaults synchronize];
1567 }
1568
1569 @end
1570
1571 @implementation VLCPlaylist (NSOutlineViewDataSource)
1572
1573 - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item
1574 {
1575     id o_value = [super outlineView: outlineView child: index ofItem: item];
1576
1577     [o_outline_dict setObject:o_value forKey:[NSString stringWithFormat:@"%p", [o_value pointerValue]]];
1578     return o_value;
1579 }
1580
1581 /* Required for drag & drop and reordering */
1582 - (BOOL)outlineView:(NSOutlineView *)outlineView writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pboard
1583 {
1584     playlist_t *p_playlist = pl_Get(VLCIntf);
1585
1586     /* First remove the items that were moved during the last drag & drop
1587        operation */
1588     [o_items_array removeAllObjects];
1589     [o_nodes_array removeAllObjects];
1590
1591     NSUInteger itemCount = [items count];
1592
1593     for (NSUInteger i = 0 ; i < itemCount ; i++) {
1594         id o_item = [items objectAtIndex:i];
1595
1596         /* Fill the items and nodes to move in 2 different arrays */
1597         if (((playlist_item_t *)[o_item pointerValue])->i_children > 0)
1598             [o_nodes_array addObject: o_item];
1599         else
1600             [o_items_array addObject: o_item];
1601     }
1602
1603     /* Now we need to check if there are selected items that are in already
1604        selected nodes. In that case, we only want to move the nodes */
1605     [self removeItemsFrom: o_nodes_array ifChildrenOf: o_nodes_array];
1606     [self removeItemsFrom: o_items_array ifChildrenOf: o_nodes_array];
1607
1608     /* We add the "VLCPlaylistItemPboardType" type to be able to recognize
1609        a Drop operation coming from the playlist. */
1610
1611     [pboard declareTypes: [NSArray arrayWithObject:@"VLCPlaylistItemPboardType"] owner: self];
1612     [pboard setData:[NSData data] forType:@"VLCPlaylistItemPboardType"];
1613
1614     return YES;
1615 }
1616
1617 - (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id <NSDraggingInfo>)info proposedItem:(id)item proposedChildIndex:(NSInteger)index
1618 {
1619     playlist_t *p_playlist = pl_Get(VLCIntf);
1620     NSPasteboard *o_pasteboard = [info draggingPasteboard];
1621
1622     if (!p_playlist) return NSDragOperationNone;
1623
1624     /* Dropping ON items is not allowed if item is not a node */
1625     if (item) {
1626         if (index == NSOutlineViewDropOnItemIndex &&
1627                 ((playlist_item_t *)[item pointerValue])->i_children == -1) {
1628             return NSDragOperationNone;
1629         }
1630     }
1631
1632     /* We refuse to drop an item in anything else than a child of the General
1633        Node. We still accept items that would be root nodes of the outlineview
1634        however, to allow drop in an empty playlist. */
1635     if (!(([self isItem: [item pointerValue] inNode: p_playlist->p_local_category checkItemExistence: NO locked: NO] ||
1636             (var_CreateGetBool(p_playlist, "media-library") && [self isItem: [item pointerValue] inNode: p_playlist->p_ml_category checkItemExistence: NO locked: NO])) || item == nil)) {
1637         return NSDragOperationNone;
1638     }
1639
1640     /* Drop from the Playlist */
1641     if ([[o_pasteboard types] containsObject: @"VLCPlaylistItemPboardType"]) {
1642         NSUInteger count = [o_nodes_array count];
1643         for (NSUInteger i = 0 ; i < count ; i++) {
1644             /* We refuse to Drop in a child of an item we are moving */
1645             if ([self isItem: [item pointerValue] inNode: [[o_nodes_array objectAtIndex:i] pointerValue] checkItemExistence: NO locked:NO]) {
1646                 return NSDragOperationNone;
1647             }
1648         }
1649         return NSDragOperationMove;
1650     }
1651     /* Drop from the Finder */
1652     else if ([[o_pasteboard types] containsObject: NSFilenamesPboardType]) {
1653         return NSDragOperationGeneric;
1654     }
1655     return NSDragOperationNone;
1656 }
1657
1658 - (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id <NSDraggingInfo>)info item:(id)item childIndex:(NSInteger)index
1659 {
1660     playlist_t * p_playlist =  pl_Get(VLCIntf);
1661     NSPasteboard *o_pasteboard = [info draggingPasteboard];
1662
1663     /* Drag & Drop inside the playlist */
1664     if ([[o_pasteboard types] containsObject: @"VLCPlaylistItemPboardType"]) {
1665         if (index == -1) // this is no valid target, sanitize to top of table
1666             index = 0;
1667
1668         int i_row = 0;
1669         playlist_item_t *p_new_parent, *p_item = NULL;
1670         NSArray *o_all_items = [o_nodes_array arrayByAddingObjectsFromArray: o_items_array];
1671         /* If the item is to be dropped as root item of the outline, make it a
1672            child of the respective general node, if is either the pl or the ml
1673            Else, choose the proposed parent as parent. */
1674         if (item == nil) {
1675             if ([self currentPlaylistRoot] == p_playlist->p_local_category || [self currentPlaylistRoot] == p_playlist->p_ml_category)
1676                 p_new_parent = [self currentPlaylistRoot];
1677             else
1678                 return NO;
1679         }
1680         else
1681             p_new_parent = [item pointerValue];
1682
1683         /* Make sure the proposed parent is a node.
1684            (This should never be true) */
1685         if (p_new_parent->i_children < 0)
1686             return NO;
1687
1688         NSUInteger count = [o_all_items count];
1689         if (count == 0)
1690             return NO;
1691
1692         playlist_item_t **pp_items = (playlist_item_t **)calloc(count, sizeof(playlist_item_t*));
1693         if (!pp_items)
1694             return NO;
1695
1696         PL_LOCK;
1697         NSUInteger j = 0;
1698         for (NSUInteger i = 0; i < count; i++) {
1699             p_item = [[o_all_items objectAtIndex:i] pointerValue];
1700             if (p_item)
1701                 pp_items[j++] = p_item;
1702         }
1703
1704         if (j == 0 || playlist_TreeMoveMany(p_playlist, j, pp_items, p_new_parent, index) != VLC_SUCCESS) {
1705             PL_UNLOCK;
1706             free(pp_items);
1707             return NO;
1708         }
1709
1710         PL_UNLOCK;
1711         free(pp_items);
1712
1713         [self playlistUpdated];
1714         i_row = [o_outline_view rowForItem:[o_outline_dict objectForKey:[NSString stringWithFormat: @"%p", [[o_all_items objectAtIndex:0] pointerValue]]]];
1715
1716         if (i_row == -1)
1717             i_row = [o_outline_view rowForItem:[o_outline_dict objectForKey:[NSString stringWithFormat: @"%p", p_new_parent]]];
1718
1719         [o_outline_view deselectAll: self];
1720         [o_outline_view selectRowIndexes:[NSIndexSet indexSetWithIndex:i_row] byExtendingSelection:NO];
1721         [o_outline_view scrollRowToVisible: i_row];
1722
1723         return YES;
1724     }
1725
1726     else if ([[o_pasteboard types] containsObject: NSFilenamesPboardType]) {
1727         if ([self currentPlaylistRoot] != p_playlist->p_local_category && [self currentPlaylistRoot] != p_playlist->p_ml_category)
1728             return NO;
1729
1730         playlist_item_t *p_node = [item pointerValue];
1731
1732         NSArray *o_values = [[o_pasteboard propertyListForType: NSFilenamesPboardType]
1733                                 sortedArrayUsingSelector: @selector(caseInsensitiveCompare:)];
1734         NSUInteger count = [o_values count];
1735         NSMutableArray *o_array = [NSMutableArray arrayWithCapacity:count];
1736         input_thread_t * p_input = pl_CurrentInput(VLCIntf);
1737
1738         if (count == 1 && p_input) {
1739             int i_result = input_AddSubtitleOSD(p_input, vlc_path2uri([[o_values objectAtIndex:0] UTF8String], NULL), true, true);
1740             vlc_object_release(p_input);
1741             if (i_result == VLC_SUCCESS)
1742                 return YES;
1743         }
1744         else if (p_input)
1745             vlc_object_release(p_input);
1746
1747         for (NSUInteger i = 0; i < count; i++) {
1748             NSDictionary *o_dic;
1749             char *psz_uri = vlc_path2uri([[o_values objectAtIndex:i] UTF8String], NULL);
1750             if (!psz_uri)
1751                 continue;
1752
1753             o_dic = [NSDictionary dictionaryWithObject:[NSString stringWithCString:psz_uri encoding:NSUTF8StringEncoding] forKey:@"ITEM_URL"];
1754
1755             free(psz_uri);
1756
1757             [o_array addObject: o_dic];
1758         }
1759
1760         if (item == nil)
1761             [self appendArray:o_array atPos:index enqueue: YES];
1762         else {
1763             assert(p_node->i_children != -1);
1764             [self appendNodeArray:o_array inNode: p_node atPos:index enqueue:YES];
1765         }
1766         return YES;
1767     }
1768     return NO;
1769 }
1770
1771 @end