macosx: Fix crash when deinitializing PLModel
[vlc.git] / modules / gui / macosx / PLModel.m
1 /*****************************************************************************
2  * PLItem.m: MacOS X interface module
3  *****************************************************************************
4  * Copyright (C) 2014 VLC authors and VideoLAN
5  * $Id$
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
20  *****************************************************************************/
21
22 #import "PLModel.h"
23
24 #import "misc.h"    /* VLCByteCountFormatter */
25
26 #import "playlist.h"
27 #import "StringUtility.h"
28 #import "intf.h"
29 #import "ControlsBar.h"
30 #import "MainMenu.h"
31 #import "playlistinfo.h"
32 #import "MainWindow.h"
33
34 #ifdef HAVE_CONFIG_H
35 # import "config.h"
36 #endif
37 #include <assert.h>
38
39 #include <vlc_playlist.h>
40 #include <vlc_input_item.h>
41 #include <vlc_input.h>
42 #include <vlc_url.h>
43
44 static int PLItemUpdated(vlc_object_t *p_this, const char *psz_var,
45                          vlc_value_t oldval, vlc_value_t new_val, void *param)
46 {
47     @autoreleasepool {
48         PLModel *model = (__bridge PLModel*)param;
49         [model performSelectorOnMainThread:@selector(plItemUpdated) withObject:nil waitUntilDone:NO];
50
51         return VLC_SUCCESS;
52     }
53 }
54
55 static int PLItemAppended(vlc_object_t *p_this, const char *psz_var,
56                           vlc_value_t oldval, vlc_value_t new_val, void *param)
57 {
58     @autoreleasepool {
59         playlist_add_t *p_add = new_val.p_address;
60         NSArray *o_val = [NSArray arrayWithObjects:[NSNumber numberWithInt:p_add->i_node], [NSNumber numberWithInt:p_add->i_item], nil];
61         PLModel *model = (__bridge PLModel*)param;
62         [model performSelectorOnMainThread:@selector(plItemAppended:) withObject:o_val waitUntilDone:NO];
63
64         return VLC_SUCCESS;
65     }
66 }
67
68 static int PLItemRemoved(vlc_object_t *p_this, const char *psz_var,
69                          vlc_value_t oldval, vlc_value_t new_val, void *param)
70 {
71     @autoreleasepool {
72         NSNumber *o_val = [NSNumber numberWithInt:new_val.i_int];
73         PLModel *model = (__bridge PLModel*)param;
74         [model performSelectorOnMainThread:@selector(plItemRemoved:) withObject:o_val waitUntilDone:NO];
75
76         return VLC_SUCCESS;
77     }
78 }
79
80 static int PlaybackModeUpdated(vlc_object_t *p_this, const char *psz_var,
81                                vlc_value_t oldval, vlc_value_t new_val, void *param)
82 {
83     @autoreleasepool {
84         PLModel *model = (__bridge PLModel*)param;
85         [model performSelectorOnMainThread:@selector(playbackModeUpdated) withObject:nil waitUntilDone:NO];
86
87         return VLC_SUCCESS;
88     }
89 }
90
91 static int VolumeUpdated(vlc_object_t *p_this, const char *psz_var,
92                          vlc_value_t oldval, vlc_value_t new_val, void *param)
93 {
94     @autoreleasepool {
95         dispatch_async(dispatch_get_main_queue(), ^{
96             [[[VLCMain sharedInstance] mainWindow] updateVolumeSlider];
97         });
98
99         return VLC_SUCCESS;
100     }
101 }
102
103 #pragma mark -
104
105 @interface PLModel ()
106 {
107     playlist_t *p_playlist;
108     __weak NSOutlineView *_outlineView;
109
110     // TODO: for transition
111     __weak VLCPlaylist *_playlist;
112     NSUInteger _retainedRowSelection;
113 }
114 @end
115
116 @implementation PLModel
117
118 #pragma mark -
119 #pragma mark Init and Stuff
120
121 - (id)initWithOutlineView:(NSOutlineView *)outlineView playlist:(playlist_t *)pl rootItem:(playlist_item_t *)root playlistObject:(id)plObj;
122 {
123     self = [super init];
124     if (self) {
125         p_playlist = pl;
126         _outlineView = outlineView;
127         _playlist = plObj;
128
129         var_AddCallback(p_playlist, "item-change", PLItemUpdated, (__bridge void *)self);
130         var_AddCallback(p_playlist, "playlist-item-append", PLItemAppended, (__bridge void *)self);
131         var_AddCallback(p_playlist, "playlist-item-deleted", PLItemRemoved, (__bridge void *)self);
132         var_AddCallback(p_playlist, "random", PlaybackModeUpdated, (__bridge void *)self);
133         var_AddCallback(p_playlist, "repeat", PlaybackModeUpdated, (__bridge void *)self);
134         var_AddCallback(p_playlist, "loop", PlaybackModeUpdated, (__bridge void *)self);
135         var_AddCallback(p_playlist, "volume", VolumeUpdated, (__bridge void *)self);
136         var_AddCallback(p_playlist, "mute", VolumeUpdated, (__bridge void *)self);
137
138         PL_LOCK;
139         _rootItem = [[PLItem alloc] initWithPlaylistItem:root];
140         [self rebuildPLItem:_rootItem];
141         PL_UNLOCK;
142     }
143
144     return self;
145 }
146
147 - (void)dealloc
148 {
149     var_DelCallback(p_playlist, "item-change", PLItemUpdated, (__bridge void *)self);
150     var_DelCallback(p_playlist, "playlist-item-append", PLItemAppended, (__bridge void *)self);
151     var_DelCallback(p_playlist, "playlist-item-deleted", PLItemRemoved, (__bridge void *)self);
152     var_DelCallback(p_playlist, "random", PlaybackModeUpdated, (__bridge void *)self);
153     var_DelCallback(p_playlist, "repeat", PlaybackModeUpdated, (__bridge void *)self);
154     var_DelCallback(p_playlist, "loop", PlaybackModeUpdated, (__bridge void *)self);
155     var_DelCallback(p_playlist, "volume", VolumeUpdated, (__bridge void *)self);
156     var_DelCallback(p_playlist, "mute", VolumeUpdated, (__bridge void *)self);
157 }
158
159 - (void)changeRootItem:(playlist_item_t *)p_root;
160 {
161     PL_ASSERT_LOCKED;
162     _rootItem = [[PLItem alloc] initWithPlaylistItem:p_root];
163     [self rebuildPLItem:_rootItem];
164
165     [_outlineView reloadData];
166     [_outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:0] byExtendingSelection:NO];
167 }
168
169 - (BOOL)hasChildren
170 {
171     return [[_rootItem children] count] > 0;
172 }
173
174 - (PLRootType)currentRootType
175 {
176     int i_root_id = [_rootItem plItemId];
177     if (i_root_id == p_playlist->p_playing->i_id)
178         return ROOT_TYPE_PLAYLIST;
179     if (p_playlist->p_media_library && i_root_id == p_playlist->p_media_library->i_id)
180         return ROOT_TYPE_MEDIALIBRARY;
181
182     return ROOT_TYPE_OTHER;
183 }
184
185 - (BOOL)editAllowed
186 {
187     return [self currentRootType] == ROOT_TYPE_MEDIALIBRARY ||
188     [self currentRootType] == ROOT_TYPE_PLAYLIST;
189 }
190
191 - (void)deleteSelectedItem
192 {
193     // check if deletion is allowed
194     if (![self editAllowed])
195         return;
196
197     NSIndexSet *selectedIndexes = [_outlineView selectedRowIndexes];
198     _retainedRowSelection = [selectedIndexes firstIndex];
199     if (_retainedRowSelection == NSNotFound)
200         _retainedRowSelection = 0;
201
202     [selectedIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
203         PLItem *item = [_outlineView itemAtRow: idx];
204         if (!item)
205             return;
206
207         // model deletion is done via callback
208         playlist_DeleteFromInput(p_playlist, [item input], pl_Unlocked);
209     }];
210 }
211
212 - (void)rebuildPLItem:(PLItem *)item
213 {
214     [item clear];
215     playlist_item_t *p_item = playlist_ItemGetById(p_playlist, [item plItemId]);
216     if (p_item) {
217         int currPos = 0;
218         for(int i = 0; i < p_item->i_children; ++i) {
219             playlist_item_t *p_child = p_item->pp_children[i];
220
221             if (p_child->i_flags & PLAYLIST_DBL_FLAG)
222                 continue;
223
224             PLItem *child = [[PLItem alloc] initWithPlaylistItem:p_child];
225             [item addChild:child atPos:currPos++];
226
227             if (p_child->i_children >= 0) {
228                 [self rebuildPLItem:child];
229             }
230
231         }
232     }
233
234 }
235
236 - (PLItem *)findItemByPlaylistId:(int)i_pl_id
237 {
238     return [self findItemInnerByPlaylistId:i_pl_id node:_rootItem];
239 }
240
241 - (PLItem *)findItemInnerByPlaylistId:(int)i_pl_id node:(PLItem *)node
242 {
243     if ([node plItemId] == i_pl_id) {
244         return node;
245     }
246
247     for (NSUInteger i = 0; i < [[node children] count]; ++i) {
248         PLItem *o_sub_item = [[node children] objectAtIndex:i];
249         if ([o_sub_item plItemId] == i_pl_id) {
250             return o_sub_item;
251         }
252
253         if (![o_sub_item isLeaf]) {
254             PLItem *o_returned = [self findItemInnerByPlaylistId:i_pl_id node:o_sub_item];
255             if (o_returned)
256                 return o_returned;
257         }
258     }
259
260     return nil;
261 }
262
263 #pragma mark -
264 #pragma mark Core events
265
266
267 - (void)plItemAppended:(NSArray *)valueArray
268 {
269     int i_node = [[valueArray firstObject] intValue];
270     int i_item = [valueArray[1] intValue];
271
272     [self addItem:i_item withParentNode:i_node];
273
274     // update badge in sidebar
275     [[[VLCMain sharedInstance] mainWindow] updateWindow];
276
277     [[NSNotificationCenter defaultCenter] postNotificationName: @"VLCMediaKeySupportSettingChanged"
278                                                         object: nil
279                                                       userInfo: nil];
280 }
281
282 - (void)plItemRemoved:(NSNumber *)value
283 {
284     int i_item = [value intValue];
285
286     [self removeItem:i_item];
287     // retain selection before deletion
288     [_outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:_retainedRowSelection] byExtendingSelection:NO];
289
290     // update badge in sidebar
291     [[[VLCMain sharedInstance] mainWindow] updateWindow];
292
293     [[NSNotificationCenter defaultCenter] postNotificationName: @"VLCMediaKeySupportSettingChanged"
294                                                         object: nil
295                                                       userInfo: nil];
296 }
297
298 - (void)plItemUpdated
299 {
300     VLCMain *instance = [VLCMain sharedInstance];
301     [[instance mainWindow] updateName];
302
303     [[VLCInfo sharedInstance] updateMetadata];
304 }
305
306 - (void)addItem:(int)i_item withParentNode:(int)i_node
307 {
308     PLItem *o_parent = [self findItemByPlaylistId:i_node];
309     if (!o_parent) {
310         return;
311     }
312
313     PL_LOCK;
314     playlist_item_t *p_item = playlist_ItemGetById(p_playlist, i_item);
315     if (!p_item || p_item->i_flags & PLAYLIST_DBL_FLAG)
316     {
317         PL_UNLOCK;
318         return;
319     }
320
321     int pos;
322     for(pos = p_item->p_parent->i_children - 1; pos >= 0; pos--)
323         if(p_item->p_parent->pp_children[pos] == p_item)
324             break;
325
326     PLItem *o_new_item = [[PLItem alloc] initWithPlaylistItem:p_item];
327     PL_UNLOCK;
328     if (pos < 0)
329         return;
330
331     [o_parent addChild:o_new_item atPos:pos];
332
333     if ([o_parent plItemId] == [_rootItem plItemId])
334         [_outlineView reloadData];
335     else // only reload leafs this way, doing it with nil collapses width of title column
336         [_outlineView reloadItem:o_parent reloadChildren:YES];
337 }
338
339 - (void)removeItem:(int)i_item
340 {
341     PLItem *o_item = [self findItemByPlaylistId:i_item];
342     if (!o_item) {
343         return;
344     }
345
346     PLItem *o_parent = [o_item parent];
347     [o_parent deleteChild:o_item];
348
349     if ([o_parent plItemId] == [_rootItem plItemId])
350         [_outlineView reloadData];
351     else
352         [_outlineView reloadItem:o_parent reloadChildren:YES];
353 }
354
355 - (void)updateItem:(input_item_t *)p_input_item
356 {
357     PL_LOCK;
358     playlist_item_t *pl_item = playlist_ItemGetByInput(p_playlist, p_input_item);
359     if (!pl_item) {
360         PL_UNLOCK;
361         return;
362     }
363     PLItem *item = [self findItemByPlaylistId:pl_item->i_id];
364     PL_UNLOCK;
365
366     if (!item)
367         return;
368
369     NSInteger row = [_outlineView rowForItem:item];
370     if (row == -1)
371         return;
372
373     [_outlineView reloadDataForRowIndexes:[NSIndexSet indexSetWithIndex:row]
374                             columnIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [[_outlineView tableColumns] count])]];
375
376 }
377
378 - (PLItem *)currentlyPlayingItem
379 {
380     PLItem *item = nil;
381
382     PL_LOCK;
383     playlist_item_t *p_current = playlist_CurrentPlayingItem(p_playlist);
384     if (p_current)
385         item = [self findItemByPlaylistId:p_current->i_id];
386     PL_UNLOCK;
387     return item;
388 }
389
390 - (void)playbackModeUpdated
391 {
392     bool loop = var_GetBool(p_playlist, "loop");
393     bool repeat = var_GetBool(p_playlist, "repeat");
394
395     VLCMainWindowControlsBar *controlsBar = (VLCMainWindowControlsBar *)[[[VLCMain sharedInstance] mainWindow] controlsBar];
396     VLCMainMenu *mainMenu = [[VLCMain sharedInstance] mainMenu];
397     if (repeat) {
398         [controlsBar setRepeatOne];
399         [mainMenu setRepeatOne];
400     } else if (loop) {
401         [controlsBar setRepeatAll];
402         [mainMenu setRepeatAll];
403     } else {
404         [controlsBar setRepeatOff];
405         [mainMenu setRepeatOff];
406     }
407
408     [controlsBar setShuffle];
409     [mainMenu setShuffle];
410 }
411
412 #pragma mark -
413 #pragma mark Sorting / Searching
414
415 - (void)sortForColumn:(NSString *)o_column withMode:(int)i_mode
416 {
417     int i_column = 0;
418     if ([o_column isEqualToString:TRACKNUM_COLUMN])
419         i_column = SORT_TRACK_NUMBER;
420     else if ([o_column isEqualToString:TITLE_COLUMN])
421         i_column = SORT_TITLE;
422     else if ([o_column isEqualToString:ARTIST_COLUMN])
423         i_column = SORT_ARTIST;
424     else if ([o_column isEqualToString:GENRE_COLUMN])
425         i_column = SORT_GENRE;
426     else if ([o_column isEqualToString:DURATION_COLUMN])
427         i_column = SORT_DURATION;
428     else if ([o_column isEqualToString:ALBUM_COLUMN])
429         i_column = SORT_ALBUM;
430     else if ([o_column isEqualToString:DESCRIPTION_COLUMN])
431         i_column = SORT_DESCRIPTION;
432     else if ([o_column isEqualToString:URI_COLUMN])
433         i_column = SORT_URI;
434     else
435         return;
436
437     PL_LOCK;
438     playlist_item_t *p_root = playlist_ItemGetById(p_playlist, [_rootItem plItemId]);
439     if (!p_root) {
440         PL_UNLOCK;
441         return;
442     }
443
444     playlist_RecursiveNodeSort(p_playlist, p_root, i_column, i_mode);
445
446     [self rebuildPLItem:_rootItem];
447     [_outlineView reloadData];
448     PL_UNLOCK;
449 }
450
451 - (void)searchUpdate:(NSString *)o_search
452 {
453     PL_LOCK;
454     playlist_item_t *p_root = playlist_ItemGetById(p_playlist, [_rootItem plItemId]);
455     if (!p_root) {
456         PL_UNLOCK;
457         return;
458     }
459     playlist_LiveSearchUpdate(p_playlist, p_root, [o_search UTF8String],
460                               true);
461     [self rebuildPLItem:_rootItem];
462     [_outlineView reloadData];
463     PL_UNLOCK;
464 }
465
466 @end
467
468 #pragma mark -
469 #pragma mark Outline view data source
470
471 @implementation PLModel(NSOutlineViewDataSource)
472
473 - (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
474 {
475     return !item ? [[_rootItem children] count] : [[item children] count];
476 }
477
478 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
479 {
480     return !item ? YES : [[item children] count] > 0;
481 }
482
483 - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item
484 {
485     id obj = !item ? _rootItem : item;
486     return [[obj children] objectAtIndex:index];
487 }
488
489 - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item
490 {
491     id o_value = nil;
492     char *psz_value;
493
494     input_item_t *p_input = [item input];
495
496     NSString * o_identifier = [tableColumn identifier];
497
498     if ([o_identifier isEqualToString:TRACKNUM_COLUMN]) {
499         psz_value = input_item_GetTrackNumber(p_input);
500         o_value = toNSStr(psz_value);
501         free(psz_value);
502
503     } else if ([o_identifier isEqualToString:TITLE_COLUMN]) {
504         psz_value = input_item_GetTitleFbName(p_input);
505         o_value = toNSStr(psz_value);
506         free(psz_value);
507
508     } else if ([o_identifier isEqualToString:ARTIST_COLUMN]) {
509         psz_value = input_item_GetArtist(p_input);
510         o_value = toNSStr(psz_value);
511         free(psz_value);
512
513     } else if ([o_identifier isEqualToString:DURATION_COLUMN]) {
514         char psz_duration[MSTRTIME_MAX_SIZE];
515         mtime_t dur = input_item_GetDuration(p_input);
516         if (dur != -1) {
517             secstotimestr(psz_duration, dur/1000000);
518             o_value = toNSStr(psz_duration);
519         }
520         else
521             o_value = @"--:--";
522
523     } else if ([o_identifier isEqualToString:GENRE_COLUMN]) {
524         psz_value = input_item_GetGenre(p_input);
525         o_value = toNSStr(psz_value);
526         free(psz_value);
527
528     } else if ([o_identifier isEqualToString:ALBUM_COLUMN]) {
529         psz_value = input_item_GetAlbum(p_input);
530         o_value = toNSStr(psz_value);
531         free(psz_value);
532
533     } else if ([o_identifier isEqualToString:DESCRIPTION_COLUMN]) {
534         psz_value = input_item_GetDescription(p_input);
535         o_value = toNSStr(psz_value);
536         free(psz_value);
537
538     } else if ([o_identifier isEqualToString:DATE_COLUMN]) {
539         psz_value = input_item_GetDate(p_input);
540         o_value = toNSStr(psz_value);
541         free(psz_value);
542
543     } else if ([o_identifier isEqualToString:LANGUAGE_COLUMN]) {
544         psz_value = input_item_GetLanguage(p_input);
545         o_value = toNSStr(psz_value);
546         free(psz_value);
547
548     } else if ([o_identifier isEqualToString:URI_COLUMN]) {
549         psz_value = decode_URI(input_item_GetURI(p_input));
550         o_value = toNSStr(psz_value);
551         free(psz_value);
552
553     } else if ([o_identifier isEqualToString:FILESIZE_COLUMN]) {
554         psz_value = input_item_GetURI(p_input);
555         if (!psz_value)
556             return @"";
557         NSURL *url = [NSURL URLWithString:toNSStr(psz_value)];
558         free(psz_value);
559         if (![url isFileURL])
560             return @"";
561
562         NSFileManager *fileManager = [NSFileManager defaultManager];
563         BOOL b_isDir;
564         if (![fileManager fileExistsAtPath:[url path] isDirectory:&b_isDir] || b_isDir)
565             return @"";
566
567         NSDictionary *attributes = [fileManager attributesOfItemAtPath:[url path] error:nil];
568         if (!attributes)
569             return @"";
570
571         o_value = [VLCByteCountFormatter stringFromByteCount:[attributes fileSize] countStyle:NSByteCountFormatterCountStyleDecimal];
572
573     } else if ([o_identifier isEqualToString:STATUS_COLUMN]) {
574         if (input_item_HasErrorWhenReading(p_input)) {
575             o_value = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kAlertCautionIcon)];
576             [o_value setSize: NSMakeSize(16,16)];
577         }
578     }
579
580     return o_value;
581 }
582
583 #pragma mark -
584 #pragma mark Drag and Drop support
585
586 - (BOOL)isItem: (PLItem *)p_item inNode: (PLItem *)p_node
587 {
588     while(p_item) {
589         if ([p_item plItemId] == [p_node plItemId]) {
590             return YES;
591         }
592
593         p_item = [p_item parent];
594     }
595
596     return NO;
597 }
598
599 - (BOOL)outlineView:(NSOutlineView *)outlineView writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pboard
600 {
601     NSUInteger itemCount = [items count];
602     _draggedItems = [[NSMutableArray alloc] initWithArray:items];
603
604     /* Add the data to the pasteboard object. */
605     [pboard declareTypes: [NSArray arrayWithObject:VLCPLItemPasteboadType] owner: self];
606     [pboard setData:[NSData data] forType:VLCPLItemPasteboadType];
607
608     return YES;
609 }
610
611 - (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id <NSDraggingInfo>)info proposedItem:(id)item proposedChildIndex:(NSInteger)index
612 {
613     NSPasteboard *o_pasteboard = [info draggingPasteboard];
614
615     /* Dropping ON items is not allowed if item is not a node */
616     if (item) {
617         if (index == NSOutlineViewDropOnItemIndex && [item isLeaf]) {
618             return NSDragOperationNone;
619         }
620     }
621
622     if (![self editAllowed])
623         return NSDragOperationNone;
624
625     /* Drop from the Playlist */
626     if ([[o_pasteboard types] containsObject:VLCPLItemPasteboadType]) {
627         NSUInteger count = [_draggedItems count];
628         for (NSUInteger i = 0 ; i < count ; i++) {
629             /* We refuse to Drop in a child of an item we are moving */
630             if ([self isItem: item inNode: [_draggedItems objectAtIndex:i]]) {
631                 return NSDragOperationNone;
632             }
633         }
634         return NSDragOperationMove;
635     }
636     /* Drop from the Finder */
637     else if ([[o_pasteboard types] containsObject: NSFilenamesPboardType]) {
638         return NSDragOperationGeneric;
639     }
640     return NSDragOperationNone;
641 }
642
643 - (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id <NSDraggingInfo>)info item:(id)targetItem childIndex:(NSInteger)index
644 {
645     NSPasteboard *o_pasteboard = [info draggingPasteboard];
646
647     if (targetItem == nil) {
648         targetItem = _rootItem;
649     }
650
651     /* Drag & Drop inside the playlist */
652     if ([[o_pasteboard types] containsObject:VLCPLItemPasteboadType]) {
653
654         NSMutableArray *o_filteredItems = [NSMutableArray arrayWithArray:_draggedItems];
655         const NSUInteger draggedItemsCount = [_draggedItems count];
656         for (NSInteger i = 0; i < [o_filteredItems count]; i++) {
657             for (NSUInteger j = 0; j < draggedItemsCount; j++) {
658                 PLItem *itemToCheck = [o_filteredItems objectAtIndex:i];
659                 PLItem *nodeToTest = [_draggedItems objectAtIndex:j];
660                 if ([itemToCheck plItemId] == [nodeToTest plItemId])
661                     continue;
662
663                 if ([self isItem:itemToCheck inNode:nodeToTest]) {
664                     [o_filteredItems removeObjectAtIndex:i];
665                     --i;
666                     break;
667                 }
668             }
669         }
670
671         NSUInteger count = [o_filteredItems count];
672         if (count == 0)
673             return NO;
674
675         playlist_item_t **pp_items = (playlist_item_t **)calloc(count, sizeof(playlist_item_t*));
676         if (!pp_items)
677             return NO;
678
679         PL_LOCK;
680         playlist_item_t *p_new_parent = playlist_ItemGetById(p_playlist, [targetItem plItemId]);
681         if (!p_new_parent) {
682             PL_UNLOCK;
683             return NO;
684         }
685
686         NSUInteger j = 0;
687         for (NSUInteger i = 0; i < count; i++) {
688             playlist_item_t *p_item = playlist_ItemGetById(p_playlist, [[o_filteredItems objectAtIndex:i] plItemId]);
689             if (p_item)
690                 pp_items[j++] = p_item;
691         }
692
693         // drop on a node itself will append entries at the end
694         if (index == NSOutlineViewDropOnItemIndex)
695             index = p_new_parent->i_children;
696
697         if (playlist_TreeMoveMany(p_playlist, j, pp_items, p_new_parent, index) != VLC_SUCCESS) {
698             PL_UNLOCK;
699             free(pp_items);
700             return NO;
701         }
702
703         PL_UNLOCK;
704         free(pp_items);
705
706         // rebuild our model
707         NSUInteger filteredItemsCount = [o_filteredItems count];
708         for(NSUInteger i = 0; i < filteredItemsCount; ++i) {
709             PLItem *o_item = [o_filteredItems objectAtIndex:i];
710             NSLog(@"delete child from parent %p", [o_item parent]);
711             [[o_item parent] deleteChild:o_item];
712             [targetItem addChild:o_item atPos:index + i];
713         }
714
715         [_outlineView reloadData];
716
717         NSMutableIndexSet *selectedIndexes = [[NSMutableIndexSet alloc] init];
718         for(NSUInteger i = 0; i < draggedItemsCount; ++i) {
719             NSInteger row = [_outlineView rowForItem:[_draggedItems objectAtIndex:i]];
720             if (row < 0)
721                 continue;
722
723             [selectedIndexes addIndex:row];
724         }
725
726         if ([selectedIndexes count] == 0)
727             [selectedIndexes addIndex:[_outlineView rowForItem:targetItem]];
728
729         [_outlineView selectRowIndexes:selectedIndexes byExtendingSelection:NO];
730
731         return YES;
732     }
733
734     else if ([[o_pasteboard types] containsObject: NSFilenamesPboardType]) {
735
736         NSArray *o_values = [[o_pasteboard propertyListForType: NSFilenamesPboardType]
737                              sortedArrayUsingSelector: @selector(caseInsensitiveCompare:)];
738         NSUInteger count = [o_values count];
739         NSMutableArray *o_array = [NSMutableArray arrayWithCapacity:count];
740         input_thread_t *p_input = playlist_CurrentInput(p_playlist);
741
742         if (count == 1 && p_input) {
743             int i_result = input_AddSubtitleOSD(p_input, vlc_path2uri([[o_values firstObject] UTF8String], NULL), true, true);
744             vlc_object_release(p_input);
745             if (i_result == VLC_SUCCESS)
746                 return YES;
747         }
748         else if (p_input)
749             vlc_object_release(p_input);
750
751         for (NSUInteger i = 0; i < count; i++) {
752             NSDictionary *o_dic;
753             char *psz_uri = vlc_path2uri([[o_values objectAtIndex:i] UTF8String], NULL);
754             if (!psz_uri)
755                 continue;
756
757             o_dic = [NSDictionary dictionaryWithObject:toNSStr(psz_uri) forKey:@"ITEM_URL"];
758             free(psz_uri);
759
760             [o_array addObject: o_dic];
761         }
762
763         // drop on a node itself will append entries at the end
764         static_assert(NSOutlineViewDropOnItemIndex == -1, "Expect NSOutlineViewDropOnItemIndex to be -1");
765
766         [_playlist addPlaylistItems:o_array withParentItemId:[targetItem plItemId] atPos:index startPlayback:NO];
767         return YES;
768     }
769     return NO;
770 }
771
772 @end