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