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