playlist.m 62.6 KB
Newer Older
1
/*****************************************************************************
2
 * playlist.m: MacOS X interface module
3
 *****************************************************************************
4
* Copyright (C) 2002-2014 VLC authors and VideoLAN
Benjamin Pracht's avatar
Benjamin Pracht committed
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 13 14 15
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
Benjamin Pracht's avatar
Benjamin Pracht committed
16
 *
17 18 19 20 21 22 23
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
Antoine Cellerier's avatar
Antoine Cellerier committed
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 30 31 32
 * reimplement enable/disable item
 */


33 34 35 36 37 38
/*****************************************************************************
 * Preamble
 *****************************************************************************/
#include <stdlib.h>                                      /* malloc(), free() */
#include <sys/param.h>                                    /* for MAXPATHLEN */
#include <string.h>
39
#include <math.h>
40
#include <sys/mount.h>
41

42 43
#import "CompatibilityFixes.h"

44
#import "intf.h"
45 46
#import "wizard.h"
#import "bookmarks.h"
47
#import "playlistinfo.h"
48 49 50
#import "playlist.h"
#import "controls.h"
#import "misc.h"
51
#import "open.h"
52
#import "MainMenu.h"
53
#import "CoreInteraction.h"
54 55 56

#include <vlc_keys.h>
#import <vlc_interface.h>
57 58
#include <vlc_url.h>

59
/*****************************************************************************
60
 * VLCPlaylistView implementation
61 62 63 64 65
 *****************************************************************************/
@implementation VLCPlaylistView

- (NSMenu *)menuForEvent:(NSEvent *)o_event
{
66
    return([(VLCPlaylist *)[self delegate] menuForEvent: o_event]);
67 68
}

69 70 71
- (void)keyDown:(NSEvent *)o_event
{
    unichar key = 0;
Benjamin Pracht's avatar
Benjamin Pracht committed
72

73
    if ([[o_event characters] length])
74 75
        key = [[o_event characters] characterAtIndex: 0];

76
    switch(key) {
77 78 79 80
        case NSDeleteCharacter:
        case NSDeleteFunctionKey:
        case NSDeleteCharFunctionKey:
        case NSBackspaceCharacter:
81
            [(VLCPlaylist *)[self delegate] deleteItem:self];
82
            break;
Benjamin Pracht's avatar
Benjamin Pracht committed
83

84 85
        case NSEnterCharacter:
        case NSCarriageReturnCharacter:
86
            [(VLCPlaylist *)[[VLCMain sharedInstance] playlist] playItem:nil];
87 88
            break;

89 90 91 92 93 94
        default:
            [super keyDown: o_event];
            break;
    }
}

95 96 97 98 99 100 101 102
- (BOOL)validateMenuItem:(NSMenuItem *)item
{
    if (([self numberOfSelectedRows] >= 1 && [item action] == @selector(delete:)) || [item action] == @selector(selectAll:))
        return YES;

    return NO;
}

103
- (BOOL)acceptsFirstResponder
104 105 106 107
{
    return YES;
}

108
- (BOOL)becomeFirstResponder
109 110 111 112 113
{
    [self setNeedsDisplay:YES];
    return YES;
}

114
- (BOOL)resignFirstResponder
115 116 117 118 119 120 121 122 123 124
{
    [self setNeedsDisplay:YES];
    return YES;
}

- (IBAction)delete:(id)sender
{
    [[[VLCMain sharedInstance] playlist] deleteItem: sender];
}

125 126 127
@end

/*****************************************************************************
128 129 130 131 132
 * VLCPlaylistCommon implementation
 *
 * This class the superclass of the VLCPlaylist and VLCPlaylistWizard.
 * It contains the common methods and elements of these 2 entities.
 *****************************************************************************/
133 134 135 136 137 138
@interface VLCPlaylistCommon ()
{
    playlist_item_t * p_current_root_item;
}
@end

139 140
@implementation VLCPlaylistCommon

141 142
- (id)init
{
143
    playlist_t * p_playlist = pl_Get(VLCIntf);
144 145
    p_current_root_item = p_playlist->p_local_category;

146
    self = [super init];
147
    if (self != nil)
148
        o_outline_dict = [[NSMutableDictionary alloc] init];
149

150
    return self;
151
}
152

153 154
- (void)awakeFromNib
{
155
    playlist_t * p_playlist = pl_Get(VLCIntf);
156 157 158
    [o_outline_view setTarget: self];
    [o_outline_view setDelegate: self];
    [o_outline_view setDataSource: self];
159
    [o_outline_view setAllowsEmptySelection: NO];
160
    [o_outline_view expandItem: [o_outline_view itemAtRow:0]];
161

162
    [o_outline_view_other setTarget: self];
163 164 165 166
    [o_outline_view_other setDelegate: self];
    [o_outline_view_other setDataSource: self];
    [o_outline_view_other setAllowsEmptySelection: NO];

167
    [[o_tc_name_other headerCell] setStringValue:_NS("Name")];
168 169
    [[o_tc_author_other headerCell] setStringValue:_NS("Author")];
    [[o_tc_duration_other headerCell] setStringValue:_NS("Duration")];
170 171

    [self reloadStyles];
172 173
}

174 175 176 177 178 179 180 181 182 183 184 185
- (void)setPlaylistRoot: (playlist_item_t *)root_item
{
    p_current_root_item = root_item;
    [o_outline_view reloadData];
    [o_outline_view_other reloadData];
}

- (playlist_item_t *)currentPlaylistRoot
{
    return p_current_root_item;
}

186 187 188 189 190 191 192 193 194 195 196
- (NSOutlineView *)outlineView
{
    return o_outline_view;
}

- (playlist_item_t *)selectedPlaylistItem
{
    return [[o_outline_view itemAtRow: [o_outline_view selectedRow]]
                                                                pointerValue];
}

197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221
- (void)reloadStyles
{
    NSFont *fontToUse;
    CGFloat rowHeight;
    if (config_GetInt(VLCIntf, "macosx-large-text")) {
        fontToUse = [NSFont systemFontOfSize:13.];
        rowHeight = 21.;
    } else {
        fontToUse = [NSFont systemFontOfSize:11.];
        rowHeight = 16.;
    }

    NSArray *columns = [o_outline_view tableColumns];
    NSUInteger count = columns.count;
    for (NSUInteger x = 0; x < count; x++)
        [[columns[x] dataCell] setFont:fontToUse];
    [o_outline_view setRowHeight:rowHeight];

    columns = [o_outline_view_other tableColumns];
    count = columns.count;
    for (NSUInteger x = 0; x < count; x++)
        [[columns[x] dataCell] setFont:fontToUse];
    [o_outline_view_other setRowHeight:rowHeight];
}

222 223 224 225 226
- (void)dealloc {
    [o_outline_dict release];
    [super dealloc];
}

227 228 229 230
@end

@implementation VLCPlaylistCommon (NSOutlineViewDataSource)
/* return the number of children for Obj-C pointer item */ /* DONE */
231
- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
232 233
{
    int i_return = 0;
234
    playlist_item_t *p_item = NULL;
235 236
    playlist_t * p_playlist = pl_Get(VLCIntf);
    //assert(outlineView == o_outline_view);
237

238
    PL_LOCK;
239
    if (!item)
240
        p_item = p_current_root_item;
241
    else
242
        p_item = (playlist_item_t *)[item pointerValue];
243

244
    if (p_item)
245
        i_return = p_item->i_children;
246
    PL_UNLOCK;
247

248
    return i_return > 0 ? i_return : 0;
249 250 251
}

/* return the child at index for the Obj-C pointer item */ /* DONE */
252
- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item
253
{
254 255
    playlist_item_t *p_return = NULL, *p_item = NULL;
    NSValue *o_value;
256
    playlist_t * p_playlist = pl_Get(VLCIntf);
257

258
    PL_LOCK;
259 260
    if (item == nil)
        p_item = p_current_root_item; /* root object */
261
    else
262
        p_item = (playlist_item_t *)[item pointerValue];
263 264

    if (p_item && index < p_item->i_children && index >= 0)
265
        p_return = p_item->pp_children[index];
266 267
    PL_UNLOCK;

268
    o_value = [o_outline_dict objectForKey:[NSString stringWithFormat: @"%p", p_return]];
269

270
    if (o_value == nil) {
271
        /* FIXME: Why is there a warning if that happens all the time and seems
272
         * to be normal? Add an assert and fix it.
273
         * msg_Warn(VLCIntf, "playlist item misses pointer value, adding one"); */
274 275
        o_value = [[NSValue valueWithPointer: p_return] retain];
    }
276 277 278 279 280 281 282
    return o_value;
}

/* is the item expandable */
- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
{
    int i_return = 0;
283
    playlist_t *p_playlist = pl_Get(VLCIntf);
284

285
    PL_LOCK;
286
    if (item == nil) {
287
        /* root object */
288
        if (p_current_root_item) {
289
            i_return = p_current_root_item->i_children;
290
        }
291
    } else {
292
        playlist_item_t *p_item = (playlist_item_t *)[item pointerValue];
293
        if (p_item)
294 295
            i_return = p_item->i_children;
    }
296
    PL_UNLOCK;
297

298
    return (i_return >= 0);
299 300 301 302 303 304
}

/* retrieve the string values for the cells */
- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)o_tc byItem:(id)item
{
    id o_value = nil;
305
    char * psz_value;
306
    playlist_item_t *p_item;
307 308 309 310

    /* For error handling */
    static BOOL attempted_reload = NO;

311
    if (item == nil || ![item isKindOfClass: [NSValue class]]) {
312 313
        /* Attempt to fix the error by asking for a data redisplay
         * This might cause infinite loop, so add a small check */
314
        if (!attempted_reload) {
315 316 317 318 319
            attempted_reload = YES;
            [outlineView reloadData];
        }
        return @"error" ;
    }
320

321
    p_item = (playlist_item_t *)[item pointerValue];
322
    if (!p_item || !p_item->p_input) {
323 324
        /* Attempt to fix the error by asking for a data redisplay
         * This might cause infinite loop, so add a small check */
325
        if (!attempted_reload) {
326 327 328 329
            attempted_reload = YES;
            [outlineView reloadData];
        }
        return @"error";
330
    }
331

332
    attempted_reload = NO;
333
    NSString * o_identifier = [o_tc identifier];
334

335 336 337
    if ([o_identifier isEqualToString:TRACKNUM_COLUMN]) {
        psz_value = input_item_GetTrackNumber(p_item->p_input);
        if (psz_value) {
338
            o_value = [NSString stringWithUTF8String:psz_value];
339
            free(psz_value);
340
        }
341
    } else if ([o_identifier isEqualToString:TITLE_COLUMN]) {
Felix Paul Kühne's avatar
Felix Paul Kühne committed
342
        /* sanity check to prevent the NSString class from crashing */
343 344
        char *psz_title =  input_item_GetTitleFbName(p_item->p_input);
        if (psz_title) {
345
            o_value = [NSString stringWithUTF8String:psz_title];
346
            free(psz_title);
347
        }
348 349 350
    } else if ([o_identifier isEqualToString:ARTIST_COLUMN]) {
        psz_value = input_item_GetArtist(p_item->p_input);
        if (psz_value) {
351
            o_value = [NSString stringWithUTF8String:psz_value];
352
            free(psz_value);
353
        }
354
    } else if ([o_identifier isEqualToString:@"duration"]) {
355
        char psz_duration[MSTRTIME_MAX_SIZE];
356 357 358
        mtime_t dur = input_item_GetDuration(p_item->p_input);
        if (dur != -1) {
            secstotimestr(psz_duration, dur/1000000);
359
            o_value = [NSString stringWithUTF8String:psz_duration];
360
        }
361 362
        else
            o_value = @"--:--";
363 364 365
    } else if ([o_identifier isEqualToString:GENRE_COLUMN]) {
        psz_value = input_item_GetGenre(p_item->p_input);
        if (psz_value) {
366
            o_value = [NSString stringWithUTF8String:psz_value];
367
            free(psz_value);
368
        }
369 370 371
    } else if ([o_identifier isEqualToString:ALBUM_COLUMN]) {
        psz_value = input_item_GetAlbum(p_item->p_input);
        if (psz_value) {
372
            o_value = [NSString stringWithUTF8String:psz_value];
373
            free(psz_value);
374
        }
375 376 377
    } else if ([o_identifier isEqualToString:DESCRIPTION_COLUMN]) {
        psz_value = input_item_GetDescription(p_item->p_input);
        if (psz_value) {
378
            o_value = [NSString stringWithUTF8String:psz_value];
379
            free(psz_value);
380
        }
381 382 383
    } else if ([o_identifier isEqualToString:DATE_COLUMN]) {
        psz_value = input_item_GetDate(p_item->p_input);
        if (psz_value) {
384
            o_value = [NSString stringWithUTF8String:psz_value];
385
            free(psz_value);
386
        }
387 388 389
    } else if ([o_identifier isEqualToString:LANGUAGE_COLUMN]) {
        psz_value = input_item_GetLanguage(p_item->p_input);
        if (psz_value) {
390
            o_value = [NSString stringWithUTF8String:psz_value];
391
            free(psz_value);
392 393
        }
    }
394 395 396
    else if ([o_identifier isEqualToString:URI_COLUMN]) {
        psz_value = decode_URI(input_item_GetURI(p_item->p_input));
        if (psz_value) {
397
            o_value = [NSString stringWithUTF8String:psz_value];
398
            free(psz_value);
399
        }
400 401 402 403 404 405 406 407 408 409 410
    }
    else if ([o_identifier isEqualToString:FILESIZE_COLUMN]) {
        psz_value = input_item_GetURI(p_item->p_input);
        o_value = @"";
        if (psz_value) {
            NSURL *url = [NSURL URLWithString:[NSString stringWithUTF8String:psz_value]];
            if ([url isFileURL]) {
                NSFileManager *fileManager = [NSFileManager defaultManager];
                if ([fileManager fileExistsAtPath:[url path]]) {
                    NSError *error;
                    NSDictionary *attributes = [fileManager attributesOfItemAtPath:[url path] error:&error];
411
                    o_value = [VLCByteCountFormatter stringFromByteCount:[attributes fileSize] countStyle:NSByteCountFormatterCountStyleDecimal];
412 413 414 415
                }
            }
            free(psz_value);
        }
416
    }
417 418
    else if ([o_identifier isEqualToString:@"status"]) {
        if (input_item_HasErrorWhenReading(p_item->p_input)) {
419 420
            o_value = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kAlertCautionIcon)];
            [o_value setSize: NSMakeSize(16,16)];
421 422
        }
    }
423

424
    return o_value;
425 426 427 428 429 430 431 432 433 434 435
}

@end

/*****************************************************************************
 * VLCPlaylistWizard implementation
 *****************************************************************************/
@implementation VLCPlaylistWizard

- (IBAction)reloadOutlineView
{
436 437
    /* Only reload the outlineview if the wizard window is open since this can
       be quite long on big playlists */
438
    if ([[o_outline_view window] isVisible])
439
        [o_outline_view reloadData];
440 441 442 443
}

@end

444
/*****************************************************************************
445 446 447 448 449
 * An extension to NSOutlineView's interface to fix compilation warnings
 * and let us access these 2 functions properly.
 * This uses a private API, but works fine on all current OSX releases.
 * Radar ID 11739459 request a public API for this. However, it is probably
 * easier and faster to recreate similar looking bitmaps ourselves.
450 451 452 453 454 455 456 457
 *****************************************************************************/

@interface NSOutlineView (UndocumentedSortImages)
+ (NSImage *)_defaultTableHeaderSortImage;
+ (NSImage *)_defaultTableHeaderReverseSortImage;
@end


458 459
/*****************************************************************************
 * VLCPlaylist implementation
460
 *****************************************************************************/
461 462 463 464 465 466 467 468 469 470 471
@interface VLCPlaylist ()
{
    NSImage *o_descendingSortingImage;
    NSImage *o_ascendingSortingImage;

    NSMutableArray *o_nodes_array;
    NSMutableArray *o_items_array;

    BOOL b_selected_item_met;
    BOOL b_isSortDescending;
    id o_tc_sortColumn;
Felix Paul Kühne's avatar
Felix Paul Kühne committed
472
    NSInteger retainedRowSelection;
473 474 475

    BOOL b_playlistmenu_nib_loaded;
    BOOL b_view_setup;
476 477
}

478 479 480
- (void)saveTableColumns;
@end

481 482
@implementation VLCPlaylist

David Fuhrmann's avatar
David Fuhrmann committed
483 484
+ (void)initialize
{
485
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
David Fuhrmann's avatar
David Fuhrmann committed
486
    NSMutableArray *o_columnArray = [[NSMutableArray alloc] init];
487 488 489
    [o_columnArray addObject: [NSArray arrayWithObjects:TITLE_COLUMN, [NSNumber numberWithFloat:190.], nil]];
    [o_columnArray addObject: [NSArray arrayWithObjects:ARTIST_COLUMN, [NSNumber numberWithFloat:95.], nil]];
    [o_columnArray addObject: [NSArray arrayWithObjects:DURATION_COLUMN, [NSNumber numberWithFloat:95.], nil]];
490 491 492 493 494

    NSDictionary *appDefaults = [NSDictionary dictionaryWithObjectsAndKeys:
                                 [NSArray arrayWithArray:o_columnArray], @"PlaylistColumnSelection",
                                 [NSArray array], @"recentlyPlayedMediaList",
                                 [NSDictionary dictionary], @"recentlyPlayedMedia", nil];
495 496 497 498 499

    [defaults registerDefaults:appDefaults];
    [o_columnArray release];
}

500 501 502
- (id)init
{
    self = [super init];
503
    if (self != nil) {
Benjamin Pracht's avatar
Benjamin Pracht committed
504 505
        o_nodes_array = [[NSMutableArray alloc] init];
        o_items_array = [[NSMutableArray alloc] init];
506 507 508 509
    }
    return self;
}

510 511 512 513 514 515 516
- (void)dealloc
{
    [o_nodes_array release];
    [o_items_array release];
    [super dealloc];
}

517 518
- (void)awakeFromNib
{
519 520 521
    if (b_view_setup)
        return;

522
    playlist_t * p_playlist = pl_Get(VLCIntf);
523

524
    [super awakeFromNib];
David Fuhrmann's avatar
David Fuhrmann committed
525
    [self initStrings];
526

527
    [o_outline_view setDoubleAction: @selector(playItem:)];
528
    [o_outline_view_other setDoubleAction: @selector(playItem:)];
529

530
    [o_outline_view registerForDraggedTypes: [NSArray arrayWithObjects:NSFilenamesPboardType, @"VLCPlaylistItemPboardType", nil]];
531
    [o_outline_view setIntercellSpacing: NSMakeSize (0.0, 1.0)];
532

533
    [o_outline_view_other registerForDraggedTypes: [NSArray arrayWithObjects:NSFilenamesPboardType, @"VLCPlaylistItemPboardType", nil]];
534 535
    [o_outline_view_other setIntercellSpacing: NSMakeSize (0.0, 1.0)];

536 537 538
    /* This uses a private API, but works fine on all current OSX releases.
     * Radar ID 11739459 request a public API for this. However, it is probably
     * easier and faster to recreate similar looking bitmaps ourselves. */
539 540
    o_ascendingSortingImage = [[NSOutlineView class] _defaultTableHeaderSortImage];
    o_descendingSortingImage = [[NSOutlineView class] _defaultTableHeaderReverseSortImage];
Benjamin Pracht's avatar
Benjamin Pracht committed
541

542
    o_tc_sortColumn = nil;
543 544 545 546 547 548

    NSArray * o_columnArray = [[NSUserDefaults standardUserDefaults] arrayForKey:@"PlaylistColumnSelection"];
    NSUInteger count = [o_columnArray count];

    id o_menu = [[VLCMain sharedInstance] mainMenu];
    NSString * o_column;
549 550 551 552

    NSMenu *o_context_menu = [o_menu setupPlaylistTableColumnsMenu];
    [o_playlist_header setMenu: o_context_menu];

553
    for (NSUInteger i = 0; i < count; i++) {
554
        o_column = [[o_columnArray objectAtIndex:i] objectAtIndex:0];
555 556 557
        if ([o_column isEqualToString:@"status"])
            continue;

558 559 560
        if(![o_menu setPlaylistColumnTableState: NSOnState forColumn: o_column])
            continue;

561
        [[o_outline_view tableColumnWithIdentifier: o_column] setWidth: [[[o_columnArray objectAtIndex:i] objectAtIndex:1] floatValue]];
562 563 564
    }

    [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(applicationWillTerminate:) name: NSApplicationWillTerminateNotification object: nil];
565 566

    b_view_setup = YES;
567 568 569 570 571 572
}

- (void)applicationWillTerminate:(NSNotification *)notification
{
    /* 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 */
    [self saveTableColumns];
573
}
574

575 576 577 578 579
- (void)searchfieldChanged:(NSNotification *)o_notification
{
    [o_search_field setStringValue:[[o_notification object] stringValue]];
}

580 581
- (void)initStrings
{
Jon Lech Johansen's avatar
Jon Lech Johansen committed
582 583
    [o_mi_play setTitle: _NS("Play")];
    [o_mi_delete setTitle: _NS("Delete")];
584
    [o_mi_recursive_expand setTitle: _NS("Expand Node")];
Jon Lech Johansen's avatar
Jon Lech Johansen committed
585
    [o_mi_selectall setTitle: _NS("Select All")];
586 587
    [o_mi_info setTitle: _NS("Media Information...")];
    [o_mi_dl_cover_art setTitle: _NS("Download Cover Art")];
588
    [o_mi_preparse setTitle: _NS("Fetch Meta Data")];
589 590 591
    [o_mi_revealInFinder setTitle: _NS("Reveal in Finder")];
    [o_mm_mi_revealInFinder setTitle: _NS("Reveal in Finder")];
    [[o_mm_mi_revealInFinder menu] setAutoenablesItems: NO];
592 593
    [o_mi_sort_name setTitle: _NS("Sort Node by Name")];
    [o_mi_sort_author setTitle: _NS("Sort Node by Author")];
Benjamin Pracht's avatar
Benjamin Pracht committed
594

595
    [o_search_field setToolTip: _NS("Search in Playlist")];
596 597
}

598 599
- (void)playlistUpdated
{
600
    /* Clear indications of any existing column sorting */
601
    NSUInteger count = [[o_outline_view tableColumns] count];
602
    for (NSUInteger i = 0 ; i < count ; i++)
603
        [o_outline_view setIndicatorImage:nil inTableColumn: [[o_outline_view tableColumns] objectAtIndex:i]];
604 605 606

    [o_outline_view setHighlightedTableColumn:nil];
    o_tc_sortColumn = nil;
Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
607 608
    // TODO Find a way to keep the dict size to a minimum
    //[o_outline_dict removeAllObjects];
609
    [o_outline_view reloadData];
610 611
    [[[[VLCMain sharedInstance] wizard] playlistWizard] reloadOutlineView];
    [[[[VLCMain sharedInstance] bookmarks] dataTable] reloadData];
612

613 614
    [o_outline_view selectRowIndexes:[NSIndexSet indexSetWithIndex:retainedRowSelection] byExtendingSelection:NO];

615
    [self outlineViewSelectionDidChange: nil];
616
    [[VLCMain sharedInstance] updateMainWindow];
617
}
618

619 620 621 622 623
- (void)outlineViewSelectionDidChange:(NSNotification *)notification
{
    // FIXME: unsafe
    playlist_item_t * p_item = [[o_outline_view itemAtRow:[o_outline_view selectedRow]] pointerValue];

624
    if (p_item) {
625 626
        /* update the state of our Reveal-in-Finder menu items */
        NSMutableString *o_mrl;
627
        char *psz_uri = input_item_GetURI(p_item->p_input);
628 629 630

        [o_mi_revealInFinder setEnabled: NO];
        [o_mm_mi_revealInFinder setEnabled: NO];
631
        if (psz_uri) {
632
            o_mrl = [NSMutableString stringWithUTF8String: psz_uri];
633

634 635
            /* perform some checks whether it is a file and if it is local at all... */
            NSRange prefix_range = [o_mrl rangeOfString: @"file:"];
636
            if (prefix_range.location != NSNotFound)
637
                [o_mrl deleteCharactersInRange: prefix_range];
638

639
            if ([o_mrl characterAtIndex:0] == '/') {
640 641 642
                [o_mi_revealInFinder setEnabled: YES];
                [o_mm_mi_revealInFinder setEnabled: YES];
            }
643
            free(psz_uri);
644
        }
645

646 647
        /* update our info-panel to reflect the new item */
        [[[VLCMain sharedInstance] info] updatePanelWithItem:p_item->p_input];
648 649 650 651 652 653 654 655
    }
}

- (BOOL)isSelectionEmpty
{
    return [o_outline_view selectedRow] == -1;
}

656 657
- (void)updateRowSelection
{
658
    // FIXME: unsafe
659
    playlist_t *p_playlist = pl_Get(VLCIntf);
Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
660 661
    playlist_item_t *p_item, *p_temp_item;
    NSMutableArray *o_array = [NSMutableArray array];
662

663
    PL_LOCK;
664 665
    p_item = playlist_CurrentPlayingItem(p_playlist);
    if (p_item == NULL) {
666
        PL_UNLOCK;
667 668
        return;
    }
669

670
    p_temp_item = p_item;
671
    while(p_temp_item->p_parent) {
672
        [o_array insertObject: [NSValue valueWithPointer: p_temp_item] atIndex: 0];
673
        p_temp_item = p_temp_item->p_parent;
674
    }
675
    PL_UNLOCK;
676

677
    NSUInteger count = [o_array count];
678
    for (NSUInteger j = 0; j < count - 1; j++) {
679
        id o_item;
680
        if ((o_item = [o_outline_dict objectForKey:
681
                            [NSString stringWithFormat: @"%p",
682
                            [[o_array objectAtIndex:j] pointerValue]]]) != nil) {
683
            [o_outline_view expandItem: o_item];
684
        }
685
    }
686 687 688 689

    id o_item = [o_outline_dict objectForKey:[NSString stringWithFormat: @"%p", p_item]];
    NSInteger i_index = [o_outline_view rowForItem:o_item];
    [o_outline_view selectRowIndexes:[NSIndexSet indexSetWithIndex:i_index] byExtendingSelection:NO];
690
    [o_outline_view setNeedsDisplay:YES];
691 692
}

693 694
/* Check if p_item is a child of p_node recursively. We need to check the item
   existence first since OSX sometimes tries to redraw items that have been
695
   deleted. We don't do it when not required since this verification takes
696
   quite a long time on big playlists (yes, pretty hacky). */
697

698
- (BOOL)isItem: (playlist_item_t *)p_item inNode: (playlist_item_t *)p_node checkItemExistence:(BOOL)b_check locked:(BOOL)b_locked
699
{
700
    playlist_t * p_playlist = pl_Get(VLCIntf);
701 702
    playlist_item_t *p_temp_item = p_item;

703 704 705
    if (!p_node)
        return NO;

706
    if (p_node == p_item)
707 708
        return YES;

709
    if (p_node->i_children < 1)
710
        return NO;
Benjamin Pracht's avatar
Benjamin Pracht committed
711

712
    if (p_temp_item) {
713
        int i;
714
        if (!b_locked) PL_LOCK;
715

716
        if (b_check) {
717 718 719
        /* Since outlineView: willDisplayCell:... may call this function with
           p_items that don't exist anymore, first check if the item is still
           in the playlist. Any cleaner solution welcomed. */
720 721
            for (i = 0; i < p_playlist->all_items.i_size; i++) {
                if (ARRAY_VAL(p_playlist->all_items, i) == p_item)
722
                    break;
723
                else if (i == p_playlist->all_items.i_size - 1)
724
                {
725
                    if (!b_locked) PL_UNLOCK;
726 727
                    return NO;
                }
728 729
            }
        }
730

731
        while(p_temp_item) {
732
            p_temp_item = p_temp_item->p_parent;
733 734
            if (p_temp_item == p_node) {
                if (!b_locked) PL_UNLOCK;
735
                return YES;
736
            }
737
        }
738
        if (!b_locked) PL_UNLOCK;
739 740 741 742
    }
    return NO;
}

Rémi Denis-Courmont's avatar
Typos  
Rémi Denis-Courmont committed
743
/* This method is useful for instance to remove the selected children of an
744
   already selected node */
745 746
- (void)removeItemsFrom:(id)o_items ifChildrenOf:(id)o_nodes
{
747 748
    NSUInteger itemCount = [o_items count];
    NSUInteger nodeCount = [o_nodes count];
749 750 751 752
    for (NSUInteger i = 0 ; i < itemCount ; i++) {
        for (NSUInteger j = 0 ; j < nodeCount ; j++) {
            if (o_items == o_nodes) {
                if (j == i) continue;
753
            }
754 755
            if ([self isItem: [[o_items objectAtIndex:i] pointerValue]
                    inNode: [[o_nodes objectAtIndex:j] pointerValue]
756
                    checkItemExistence: NO locked:NO]) {
757
                [o_items removeObjectAtIndex:i];
758 759 760 761 762 763 764 765 766
                /* We need to execute the next iteration with the same index
                   since the current item has been deleted */
                i--;
                break;
            }
        }
    }
}

767 768
- (IBAction)savePlaylist:(id)sender
{
769
    playlist_t * p_playlist = pl_Get(VLCIntf);
770 771

    NSSavePanel *o_save_panel = [NSSavePanel savePanel];
772 773
    NSString * o_name = [NSString stringWithFormat: @"%@", _NS("Untitled")];

774 775 776 777 778 779 780
    [NSBundle loadNibNamed:@"PlaylistAccessoryView" owner:self];

    [o_save_accessory_text setStringValue: _NS("File Format:")];
    [[o_save_accessory_popup itemAtIndex:0] setTitle: _NS("Extended M3U")];
    [[o_save_accessory_popup itemAtIndex:1] setTitle: _NS("XML Shareable Playlist Format (XSPF)")];
    [[o_save_accessory_popup itemAtIndex:2] setTitle: _NS("HTML playlist")];

781 782
    [o_save_panel setTitle: _NS("Save Playlist")];
    [o_save_panel setPrompt: _NS("Save")];
783
    [o_save_panel setAccessoryView: o_save_accessory_view];
784
    [o_save_panel setNameFieldStringValue: o_name];
785

786
    if ([o_save_panel runModal] == NSFileHandlingPanelOKButton) {
787
        NSString *o_filename = [[o_save_panel URL] path];
788

789
        if ([o_save_accessory_popup indexOfSelectedItem] == 0) {
790 791 792 793 794
            NSString * o_real_filename;
            NSRange range;
            range.location = [o_filename length] - [@".m3u" length];
            range.length = [@".m3u" length];

795
            if ([o_filename compare:@".m3u" options: NSCaseInsensitiveSearch range: range] != NSOrderedSame)
796 797 798
                o_real_filename = [NSString stringWithFormat: @"%@.m3u", o_filename];
            else
                o_real_filename = o_filename;
799 800

            playlist_Export(p_playlist,
801
                [o_real_filename fileSystemRepresentation],
802 803
                p_playlist->p_local_category, "export-m3u");
        } else if ([o_save_accessory_popup indexOfSelectedItem] == 1) {
804 805 806 807 808
            NSString * o_real_filename;
            NSRange range;
            range.location = [o_filename length] - [@".xspf" length];
            range.length = [@".xspf" length];

809
            if ([o_filename compare:@".xspf" options: NSCaseInsensitiveSearch range: range] != NSOrderedSame)
810 811 812
                o_real_filename = [NSString stringWithFormat: @"%@.xspf", o_filename];
            else
                o_real_filename = o_filename;
813 814

            playlist_Export(p_playlist,
815
                [o_real_filename fileSystemRepresentation],
816 817
                p_playlist->p_local_category, "export-xspf");
        } else {
818 819
            NSString * o_real_filename;
            NSRange range;
820 821
            range.location = [o_filename length] - [@".html" length];
            range.length = [@".html" length];
822

823
            if ([o_filename compare:@".html" options: NSCaseInsensitiveSearch range: range] != NSOrderedSame)
824
                o_real_filename = [NSString stringWithFormat: @"%@.html", o_filename];
825 826
            else
                o_real_filename = o_filename;
827 828

            playlist_Export(p_playlist,
Felix Paul Kühne's avatar
Felix Paul Kühne committed
829
                [o_real_filename fileSystemRepresentation],
830
                p_playlist->p_local_category, "export-html");
831
        }
832 833 834
    }
}

Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
835
/* When called retrieves the selected outlineview row and plays that node or item */
836 837 838
- (IBAction)playItem:(id)sender
{
    intf_thread_t * p_intf = VLCIntf;
839
    playlist_t * p_playlist = pl_Get(p_intf);
840

841 842
    playlist_item_t *p_item;
    playlist_item_t *p_node = NULL;
Benjamin Pracht's avatar
 
Benjamin Pracht committed
843

844
    // ignore clicks on column header when handling double action
845
    if (sender != nil && [o_outline_view clickedRow] == -1 && sender != o_mi_play)
846 847
        return;

848
    p_item = [[o_outline_view itemAtRow:[o_outline_view selectedRow]] pointerValue];
Benjamin Pracht's avatar
 
Benjamin Pracht committed
849

850
    PL_LOCK;
851 852
    if (p_item) {
        if (p_item->i_children == -1) {
853
            p_node = p_item->p_parent;
854
        } else {
855
            p_node = p_item;
856
            if (p_node->i_children > 0 && p_node->pp_children[0]->i_children == -1)
857
                p_item = p_node->pp_children[0];
Benjamin Pracht's avatar
 
Benjamin Pracht committed
858
            else
859
                p_item = NULL;
Benjamin Pracht's avatar
 
Benjamin Pracht committed
860
        }
861

862
        playlist_Control(p_playlist, PLAYLIST_VIEWPLAY, pl_Locked, p_node, p_item);
863
    }
864
    PL_UNLOCK;
865
}
866

867 868
- (IBAction)revealItemInFinder:(id)sender
{
869 870 871 872
    NSIndexSet * selectedRows = [o_outline_view selectedRowIndexes];
    NSUInteger count = [selectedRows count];
    NSUInteger indexes[count];
    [selectedRows getIndexes:indexes maxCount:count inIndexRange:nil];
873

874 875 876 877 878
    NSMutableString * o_mrl;
    playlist_item_t *p_item;
    for (NSUInteger i = 0; i < count; i++) {
        p_item = [[o_outline_view itemAtRow:indexes[i]] pointerValue];

879
        if (! p_item || !p_item->p_input)
880 881
            continue;

882
        char * psz_url = decode_URI(input_item_GetURI(p_item->p_input));
883
        o_mrl = [[NSMutableString alloc] initWithString: [NSString stringWithUTF8String:psz_url ? psz_url : ""]];
884 885
        if (psz_url != NULL)
            free( psz_url );
886

887
        /* perform some checks whether it is a file and if it is local at all... */
888
        if ([o_mrl length] > 0) {
889
            NSRange prefix_range = [o_mrl rangeOfString: @"file:"];
890
            if (prefix_range.location != NSNotFound)
891
                [o_mrl deleteCharactersInRange: prefix_range];
892

893
            if ([o_mrl characterAtIndex:0] == '/')
894 895
                [[NSWorkspace sharedWorkspace] selectFile: o_mrl inFileViewerRootedAtPath: o_mrl];
        }
896

897 898
        [o_mrl release];
    }
899
}
900

901 902 903 904
/* When called retrieves the selected outlineview row and plays that node or item */
- (IBAction)preparseItem:(id)sender
{
    int i_count;
905
    NSIndexSet *o_selected_indexes;
906
    intf_thread_t * p_intf = VLCIntf;
907
    playlist_t * p_playlist = pl_Get(p_intf);
908 909
    playlist_item_t *p_item = NULL;

910 911 912 913 914
    o_selected_indexes = [o_outline_view selectedRowIndexes];
    i_count = [o_selected_indexes count];

    NSUInteger indexes[i_count];
    [o_selected_indexes getIndexes:indexes maxCount:i_count inIndexRange:nil];
915
    for (int i = 0; i < i_count; i++) {
916 917
        p_item = [[o_outline_view itemAtRow:indexes[i]] pointerValue];
        [o_outline_view deselectRow: indexes[i]];
918

919 920
        if (p_item) {
            if (p_item->i_children == -1)
921
                libvlc_MetaRequest(p_intf->p_libvlc, p_item->p_input, META_REQUEST_OPTION_NONE);
922
            else
923
                msg_Dbg(p_intf, "preparsing nodes not implemented");
924 925 926 927 928
        }
    }
    [self playlistUpdated];
}

929 930 931
- (IBAction)downloadCoverArt:(id)sender
{
    int i_count;
932
    NSIndexSet *o_selected_indexes;
933
    intf_thread_t * p_intf = VLCIntf;
934
    playlist_t * p_playlist = pl_Get(p_intf);
935 936
    playlist_item_t *p_item = NULL;

937 938 939 940 941
    o_selected_indexes = [o_outline_view selectedRowIndexes];
    i_count = [o_selected_indexes count];

    NSUInteger indexes[i_count];
    [o_selected_indexes getIndexes:indexes maxCount:i_count inIndexRange:nil];
942
    for (int i = 0; i < i_count; i++) {
943
        p_item = [[o_outline_view itemAtRow: indexes[i]] pointerValue];
944

945
        if (p_item && p_item->i_children == -1)
946
            libvlc_ArtRequest(p_intf->p_libvlc, p_item->p_input, META_REQUEST_OPTION_NONE);
947 948 949 950
    }
    [self playlistUpdated];
}

Benjamin Pracht's avatar
Benjamin Pracht committed
951 952 953 954 955
- (IBAction)selectAll:(id)sender
{
    [o_outline_view selectAll: nil];
}

956 957 958 959 960
- (IBAction)showInfoPanel:(id)sender
{
    [[[VLCMain sharedInstance] info] initPanel];
}

Benjamin Pracht's avatar
Benjamin Pracht committed
961 962
- (IBAction)deleteItem:(id)sender
{
963 964
    int i_count;
    NSIndexSet *o_selected_indexes;
Benjamin Pracht's avatar
Benjamin Pracht committed
965 966 967
    playlist_t * p_playlist;
    intf_thread_t * p_intf = VLCIntf;

968 969
    o_selected_indexes = [o_outline_view selectedRowIndexes];
    i_count = [o_selected_indexes count];
970
    retainedRowSelection = [o_selected_indexes firstIndex];
Benjamin Pracht's avatar
Benjamin Pracht committed
971

972
    p_playlist = pl_Get(p_intf);
973

974
    NSUInteger indexes[i_count];
975
    if (i_count == [o_outline_view numberOfRows]) {
976
        PL_LOCK;
977
        playlist_NodeDelete(p_playlist, [self currentPlaylistRoot], true, false);
978 979 980 981
        PL_UNLOCK;
        [self playlistUpdated];
        return;
    }
982
    [o_selected_indexes getIndexes:indexes maxCount:i_count inIndexRange:nil];
983
    for (int i = 0; i < i_count; i++) {
984 985
        id o_item = [o_outline_view itemAtRow: indexes[i]];
        [o_outline_view deselectRow: indexes[i]];
986 987

        PL_LOCK;
988
        playlist_item_t *p_item = [o_item pointerValue];
989 990 991 992
        if (!p_item || !p_item->p_input) {
            PL_UNLOCK;
            continue;
        }
Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
993

994
        if (p_item->i_children != -1) {
995
        //is a node and not an item
996 997 998
            if (playlist_Status(p_playlist) != PLAYLIST_STOPPED &&
                [self isItem: playlist_CurrentPlayingItem(p_playlist) inNode: ((playlist_item_t *)[o_item pointerValue])
                        checkItemExistence: NO locked:YES] == YES)
Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
999
                // if current item is in selected node and is playing then stop playlist
1000
                playlist_Control(p_playlist, PLAYLIST_STOP, pl_Locked);
1001

1002 1003 1004
                playlist_NodeDelete(p_playlist, p_item, true, false);
        } else
            playlist_DeleteFromInput(p_playlist, p_item->p_input, pl_Locked);
1005 1006

        PL_UNLOCK;
1007
        [o_outline_dict removeObjectForKey:[NSString stringWithFormat:@"%p", [o_item pointerValue]]];
1008
        [o_item release];
Benjamin Pracht's avatar
Benjamin Pracht committed
1009
    }
1010

Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
1011
    [self playlistUpdated];
Benjamin Pracht's avatar
Benjamin Pracht committed
1012
}
Benjamin Pracht's avatar
Benjamin Pracht committed
1013

1014 1015 1016 1017 1018 1019 1020
- (IBAction)sortNodeByName:(id)sender
{
    [self sortNode: SORT_TITLE];
}

- (IBAction)sortNodeByAuthor:(id)sender
{
Clément Stenac's avatar
Clément Stenac committed
1021
    [self sortNode: SORT_ARTIST];
1022 1023 1024 1025
}

- (void)sortNode:(int)i_mode
{
1026
    playlist_t * p_playlist = pl_Get(VLCIntf);
1027 1028
    playlist_item_t * p_item;

1029
    if ([o_outline_view selectedRow] > -1) {
1030
        p_item = [[o_outline_view itemAtRow: [o_outline_view selectedRow]] pointerValue];
1031 1032
        if (!p_item)
            return;
1033
    } else
1034
        p_item = [self currentPlaylistRoot]; // If no item is selected, sort the whole playlist
1035

1036
    PL_LOCK;
1037 1038
    if (p_item->i_children > -1) // the item is a node
        playlist_RecursiveNodeSort(p_playlist, p_item, i_mode, ORDER_NORMAL);
1039
    else
1040 1041
        playlist_RecursiveNodeSort(p_playlist, p_item->p_parent, i_mode, ORDER_NORMAL);

1042
    PL_UNLOCK;
1043 1044 1045
    [self playlistUpdated];
}

1046
- (input_item_t *)createItem:(NSDictionary *)o_one_item
1047
{
Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
1048
    intf_thread_t * p_intf = VLCIntf;
1049
    playlist_t * p_playlist = pl_Get(p_intf);
1050

1051
    input_item_t *p_input;
1052 1053 1054
    BOOL b_rem = FALSE, b_dir = FALSE, b_writable = FALSE;
    NSString *o_uri, *o_name, *o_path;
    NSURL * o_nsurl;
1055 1056 1057 1058 1059
    NSArray *o_options;
    NSURL *o_true_file;

    /* Get the item */
    o_uri = (NSString *)[o_one_item objectForKey: @"ITEM_URL"];
1060 1061
    o_nsurl = [NSURL URLWithString: o_uri];
    o_path = [o_nsurl path];
1062 1063 1064
    o_name = (NSString *)[o_one_item objectForKey: @"ITEM_NAME"];
    o_options = (NSArray *)[o_one_item objectForKey: @"ITEM_OPTIONS"];

1065
    if ([[NSFileManager defaultManager] fileExistsAtPath:o_path isDirectory:&b_dir] && b_dir &&
1066
        [[NSWorkspace sharedWorkspace] getFileSystemInfoForPath:o_path isRemovable: &b_rem
1067
                                                     isWritable:&b_writable isUnmountable:NULL description:NULL type:NULL] && b_rem && !b_writable && [o_nsurl isFileURL]) {
1068 1069
        id o_vlc_open = [[VLCMain sharedInstance] open];

1070
        NSString *diskType = [o_vlc_open getVolumeTypeFromMountPath: o_path];
1071
        msg_Dbg(p_intf, "detected optical media of type %s in the file input", [diskType UTF8String]);
1072

1073
        if ([diskType isEqualToString: kVLCMediaDVD])
1074
            o_uri = [NSString stringWithFormat: @"dvdnav://%@", [o_vlc_open getBSDNodeFromMountPath: o_path]];
1075
        else if ([diskType isEqualToString: kVLCMediaVideoTSFolder])
1076
            o_uri = [NSString stringWithFormat: @"dvdnav://%@", o_path];
1077
        else if ([diskType isEqualToString: kVLCMediaAudioCD])
1078
            o_uri = [NSString stringWithFormat: @"cdda://%@", [o_vlc_open getBSDNodeFromMountPath: o_path]];
1079
        else if ([diskType isEqualToString: kVLCMediaVCD])
1080
            o_uri = [NSString stringWithFormat: @"vcd://%@#0:0", [o_vlc_open getBSDNodeFromMountPath: o_path]];
1081
        else if ([diskType isEqualToString: kVLCMediaSVCD])
1082
            o_uri = [NSString stringWithFormat: @"vcd://%@@0:0", [o_vlc_open getBSDNodeFromMountPath: o_path]];
1083
        else if ([diskType isEqualToString: kVLCMediaBD] || [diskType isEqualToString: kVLCMediaBDMVFolder])
1084 1085
            o_uri = [NSString stringWithFormat: @"bluray://%@", o_path];
        else
1086
            msg_Warn(VLCIntf, "unknown disk type, treating %s as regular input", [o_path UTF8String]);
Benjamin Pracht's avatar
Benjamin Pracht committed
1087

1088
        p_input = input_item_New([o_uri UTF8String], [[[NSFileManager defaultManager] displayNameAtPath: o_path] UTF8String]);
1089
    }
1090
    else
1091
        p_input = input_item_New([o_uri fileSystemRepresentation], o_name ? [o_name UTF8String] : NULL);
1092

1093
    if (!p_input)
1094
        return NULL;
1095

1096
    if (o_options) {
1097
        NSUInteger count = [o_options count];
1098
        for (NSUInteger i = 0; i < count; i++)
1099
            input_item_AddOption(p_input, [[o_options objectAtIndex:i] UTF8String], VLC_INPUT_OPTION_TRUSTED);
1100
    }
Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
1101

1102
    /* Recent documents menu */
1103
    if (o_nsurl != nil && (BOOL)config_GetInt(p_playlist, "macosx-recentitems") == YES)
1104
        [[NSDocumentController sharedDocumentController] noteNewRecentDocumentURL: o_nsurl];
1105

1106
    return p_input;
1107 1108 1109 1110
}

- (void)appendArray:(NSArray*)o_array atPos:(int)i_position enqueue:(BOOL)b_enqueue
{
1111
    playlist_t * p_playlist = pl_Get(VLCIntf);
1112
    NSUInteger count = [o_array count];
1113 1114 1115 1116 1117
    BOOL b_usingPlaylist;
    if ([self currentPlaylistRoot] == p_playlist->p_ml_category)
        b_usingPlaylist = NO;
    else
        b_usingPlaylist = YES;
1118

1119
    PL_LOCK;
1120
    for (NSUInteger i_item = 0; i_item < count; i_item++) {
1121
        input_item_t *p_input;
1122 1123 1124
        NSDictionary *o_one_item;

        /* Get the item */
1125
        o_one_item = [o_array objectAtIndex:i_item];
1126
        p_input = [self createItem: o_one_item];
1127
        if (!p_input)
1128
            continue;
Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
1129 1130

        /* Add the item */
1131
        int returnValue = playlist_AddInput(p_playlist, p_input, PLAYLIST_INSERT, i_position == -1 ? PLAYLIST_END : i_position + i_item, b_usingPlaylist, pl_Locked);
1132
        if (returnValue != VLC_SUCCESS) {
1133
            vlc_gc_decref(p_input);
1134 1135
            continue;
        }
Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
1136

1137 1138 1139
        if (i_item == 0 && !b_enqueue) {
            playlist_item_t *p_item = playlist_ItemGetByInput(p_playlist, p_input);
            playlist_Control(p_playlist, PLAYLIST_VIEWPLAY, pl_Locked, p_item->p_parent, p_item);
1140 1141
        }

1142
        vlc_gc_decref(p_input);
1143
    }
1144
    PL_UNLOCK;
1145
    [self playlistUpdated];
1146 1147
}

1148
- (void)appendNodeArray:(NSArray*)o_array inNode:(playlist_item_t *)p_node atPos:(int)i_position enqueue:(BOOL)b_enqueue
1149
{
1150
    playlist_t * p_playlist = pl_Get(VLCIntf);
1151
    NSUInteger count = [o_array count];
1152

1153
    for (NSUInteger i_item = 0; i_item < count; i_item++) {
1154
        input_item_t *p_input;
1155 1156 1157
        NSDictionary *o_one_item;

        /* Get the item */
1158
        PL_LOCK;
1159
        o_one_item = [o_array objectAtIndex:i_item];
1160
        p_input = [self createItem: o_one_item];
1161

1162 1163
        if (!p_input)
            continue;
Benjamin Pracht's avatar
Benjamin Pracht committed
1164

1165
        /* Add the item */
1166
        playlist_NodeAddInput(p_playlist, p_input, p_node,
1167 1168
                                      PLAYLIST_INSERT,
                                      i_position == -1 ?
1169
                                      PLAYLIST_END : i_position + i_item,
1170
                                      pl_Locked);
1171

1172

1173
        if (i_item == 0 && !b_enqueue) {
1174
            playlist_item_t *p_item;
1175 1176
            p_item = playlist_ItemGetByInput(p_playlist, p_input);
            playlist_Control(p_playlist, PLAYLIST_VIEWPLAY, pl_Locked, p_node, p_item);
Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
1177
        }
1178
        PL_UNLOCK;
1179
        vlc_gc_decref(p_input);
1180
    }
1181
    [self playlistUpdated];
Jon Lech Johansen's avatar
Jon Lech Johansen committed
1182
}
1183

1184 1185
- (NSMutableArray *)subSearchItem:(playlist_item_t *)p_item
{
1186
    playlist_t *p_playlist = pl_Get(VLCIntf);
Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
1187
    playlist_item_t *p_selected_item;
1188
    int i_selected_row;
1189 1190 1191 1192 1193

    i_selected_row = [o_outline_view selectedRow];
    if (i_selected_row < 0)
        i_selected_row = 0;

1194
    p_selected_item = (playlist_item_t *)[[o_outline_view itemAtRow: i_selected_row] pointerValue];
1195

1196
    for (NSUInteger i_current = 0; i_current < p_item->i_children ; i_current++) {
Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
1197 1198
        char *psz_temp;
        NSString *o_current_name, *o_current_author;
1199

1200
        PL_LOCK;
1201
        o_current_name = [NSString stringWithUTF8String:p_item->pp_children[i_current]->p_input->psz_name];
1202
        psz_temp = input_item_GetInfo(p_item->p_input, _("Meta-information"),_("Artist"));
1203
        o_current_author = [NSString stringWithUTF8String:psz_temp];
1204
        free(psz_temp);
1205
        PL_UNLOCK;
1206

1207
        if (p_selected_item == p_item->pp_children[i_current] && b_selected_item_met == NO)
1208
            b_selected_item_met = YES;
1209
        else if (p_selected_item == p_item->pp_children[i_current] && b_selected_item_met == YES)
1210
            return NULL;
1211 1212
        else if (b_selected_item_met == YES &&
                    ([o_current_name rangeOfString:[o_search_field
1213
                        stringValue] options:NSCaseInsensitiveSearch].length ||
Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
1214
                      [o_current_author rangeOfString:[o_search_field
1215
                        stringValue] options:NSCaseInsensitiveSearch].length))
1216 1217
            /*Adds the parent items in the result array as well, so that we can
            expand the tree*/
1218 1219 1220
            return [NSMutableArray arrayWithObject: [NSValue valueWithPointer: p_item->pp_children[i_current]]];

        if (p_item->pp_children[i_current]->i_children > 0) {
1221 1222
            id o_result = [self subSearchItem:
                                            p_item->pp_children[i_current]];
1223
            if (o_result != NULL) {
1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234
                [o_result insertObject: [NSValue valueWithPointer:
                                p_item->pp_children[i_current]] atIndex:0];
                return o_result;
            }
        }
    }
    return NULL;
}

- (IBAction)searchItem:(id)sender
{
1235
    playlist_t * p_playlist = pl_Get(VLCIntf);
1236 1237 1238 1239 1240 1241
    id o_result;

    int i_row = -1;

    b_selected_item_met = NO;

1242 1243
    /* First, only search after the selected item:
     * (b_selected_item_met = NO) */
1244
    o_result = [self subSearchItem:[self currentPlaylistRoot]];
1245
    if (o_result == NULL)
1246
        /* If the first search failed, search again from the beginning */
1247
        o_result = [self subSearchItem:[self currentPlaylistRoot]];
1248 1249

    if (o_result != NULL) {
1250
        int i_start;
1251
        if ([[o_result objectAtIndex:0] pointerValue] == p_playlist->p_local_category)
1252
            i_start = 1;
1253
        else
1254 1255
            i_start = 0;
        NSUInteger count = [o_result count];
Benjamin Pracht's avatar
Benjamin Pracht committed
1256

1257
        for (NSUInteger i = i_start ; i < count - 1 ; i++) {
1258 1259
            [o_outline_view expandItem: [o_outline_dict objectForKey:
                        [NSString stringWithFormat: @"%p",
1260
                        [[o_result objectAtIndex:i] pointerValue]]]];
1261
        }
1262 1263
        i_row = [o_outline_view rowForItem: [o_outline_dict objectForKey:
                        [NSString stringWithFormat: @"%p",
1264 1265
                        [[o_result objectAtIndex:count - 1 ]
                        pointerValue]]]];
1266
    }
1267
    if (i_row > -1) {
1268
        [o_outline_view selectRowIndexes:[NSIndexSet indexSetWithIndex:i_row] byExtendingSelection:NO];
1269
        [o_outline_view scrollRowToVisible: i_row];
1270 1271 1272
    }
}

1273 1274
- (IBAction)recursiveExpandNode:(id)sender
{
1275 1276 1277 1278
    NSIndexSet * selectedRows = [o_outline_view selectedRowIndexes];
    NSUInteger count = [selectedRows count];
    NSUInteger indexes[count];
    [selectedRows getIndexes:indexes maxCount:count inIndexRange:nil];
1279

1280 1281 1282 1283 1284
    id o_item;
    playlist_item_t *p_item;
    for (NSUInteger i = 0; i < count; i++) {
        o_item = [o_outline_view itemAtRow: indexes[i]];
        p_item = (playlist_item_t *)[o_item pointerValue];
1285

1286
        if (![[o_outline_view dataSource] outlineView: o_outline_view isItemExpandable: o_item])
1287 1288 1289 1290 1291 1292 1293 1294 1295 1296
            o_item = [o_outline_dict objectForKey: [NSString stringWithFormat: @"%p", p_item->p_parent]];

        /* We need to collapse the node first, since OSX refuses to recursively
         expand an already expanded node, even if children nodes are collapsed. */
        [o_outline_view collapseItem: o_item collapseChildren: YES];
        [o_outline_view expandItem: o_item expandChildren: YES];

        selectedRows = [o_outline_view selectedRowIndexes];
        [selectedRows getIndexes:indexes maxCount:count inIndexRange:nil];
    }
1297 1298
}

Benjamin Pracht's avatar
Benjamin Pracht committed
1299 1300
- (NSMenu *)menuForEvent:(NSEvent *)o_event
{
1301 1302 1303
    if (!b_playlistmenu_nib_loaded)
        b_playlistmenu_nib_loaded = [NSBundle loadNibNamed:@"PlaylistMenu" owner:self];

Benjamin Pracht's avatar
Benjamin Pracht committed
1304
    NSPoint pt;
1305 1306
    bool b_rows;
    bool b_item_sel;
Benjamin Pracht's avatar
Benjamin Pracht committed
1307

1308
    pt = [o_outline_view convertPoint: [o_event locationInWindow] fromView: nil];
1309
    int row = [o_outline_view rowAtPoint:pt];
1310
    if (row != -1 && ![[o_outline_view selectedRowIndexes] containsIndex: row])
1311 1312
        [o_outline_view selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO];

1313
    b_item_sel = (row != -1 && [o_outline_view selectedRow] != -1);
1314
    b_rows = [o_outline_view numberOfRows] != 0;
Benjamin Pracht's avatar
Benjamin Pracht committed
1315 1316 1317 1318 1319

    [o_mi_play setEnabled: b_item_sel];
    [o_mi_delete setEnabled: b_item_sel];
    [o_mi_selectall setEnabled: b_rows];
    [o_mi_info setEnabled: b_item_sel];
1320
    [o_mi_preparse setEnabled: b_item_sel];
1321 1322 1323
    [o_mi_recursive_expand setEnabled: b_item_sel];
    [o_mi_sort_name setEnabled: b_item_sel];
    [o_mi_sort_author setEnabled: b_item_sel];
Benjamin Pracht's avatar
Benjamin Pracht committed
1324

1325
    return o_ctx_menu;
Benjamin Pracht's avatar
Benjamin Pracht committed
1326
}
1327

1328
- (void)outlineView: (NSOutlineView *)o_tv didClickTableColumn:(NSTableColumn *)o_tc
1329
{
1330
    int i_mode, i_type = 0;
Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
1331
    intf_thread_t *p_intf = VLCIntf;
1332
    NSString * o_identifier = [o_tc identifier];
1333

1334
    playlist_t *p_playlist = pl_Get(p_intf);
1335

1336
    if ([o_identifier isEqualToString:TRACKNUM_COLUMN])
1337
        i_mode = SORT_TRACK_NUMBER;
1338
    else if ([o_identifier isEqualToString:TITLE_COLUMN])
1339
        i_mode = SORT_TITLE;
1340
    else if ([o_identifier isEqualToString:ARTIST_COLUMN])
1341
        i_mode = SORT_ARTIST;
1342
    else if ([o_identifier isEqualToString:GENRE_COLUMN])
1343
        i_mode = SORT_GENRE;
1344
    else if ([o_identifier isEqualToString:DURATION_COLUMN])
1345
        i_mode = SORT_DURATION;
1346
    else if ([o_identifier isEqualToString:ALBUM_COLUMN])
1347
        i_mode = SORT_ALBUM;
1348
    else if ([o_identifier isEqualToString:DESCRIPTION_COLUMN])
1349
        i_mode = SORT_DESCRIPTION;
1350
    else if ([o_identifier isEqualToString:URI_COLUMN])
1351
        i_mode = SORT_URI;
1352
    else
1353 1354
        return;

1355
    if (o_tc_sortColumn == o_tc)
1356 1357
        b_isSortDescending = !b_isSortDescending;
    else
1358
        b_isSortDescending = false;
1359

1360
    if (b_isSortDescending)
1361 1362 1363 1364
        i_type = ORDER_REVERSE;
    else
        i_type = ORDER_NORMAL;

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
1365
    PL_LOCK;
1366
    playlist_RecursiveNodeSort(p_playlist, [self currentPlaylistRoot], i_mode, i_type);
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
1367
    PL_UNLOCK;
1368 1369 1370 1371 1372 1373

    [self playlistUpdated];

    o_tc_sortColumn = o_tc;
    [o_outline_view setHighlightedTableColumn:o_tc];

1374
    if (b_isSortDescending)
1375
        [o_outline_view setIndicatorImage:o_descendingSortingImage inTableColumn:o_tc];
1376
    else
1377
        [o_outline_view setIndicatorImage:o_ascendingSortingImage inTableColumn:o_tc];
1378 1379
}

1380 1381 1382 1383 1384 1385

- (void)outlineView:(NSOutlineView *)outlineView
                                willDisplayCell:(id)cell
                                forTableColumn:(NSTableColumn *)tableColumn
                                item:(id)item
{
1386 1387 1388 1389
    /* this method can be called when VLC is already dead, hence the extra checks */
    intf_thread_t * p_intf = VLCIntf;
    if (!p_intf)
        return;
1390
    playlist_t *p_playlist = pl_Get(p_intf);
1391 1392
    if (!p_playlist)
        return;
1393 1394

    id o_playing_item;
1395

1396
    PL_LOCK;
1397
    o_playing_item = [o_outline_dict objectForKey: [NSString stringWithFormat:@"%p",  playlist_CurrentPlayingItem(p_playlist)]];
1398
    PL_UNLOCK;
1399

1400 1401 1402 1403 1404 1405
    NSFont *fontToUse;
    if (config_GetInt(VLCIntf, "macosx-large-text"))
        fontToUse = [NSFont systemFontOfSize:13.];
    else
        fontToUse = [NSFont systemFontOfSize:11.];

1406 1407
    if ([self isItem: [o_playing_item pointerValue] inNode: [item pointerValue] checkItemExistence:YES locked:NO]
                        || [o_playing_item isEqual: item])
1408
        [cell setFont: [[NSFontManager sharedFontManager] convertFont:fontToUse toHaveTrait:NSBoldFontMask]];
1409
    else
1410
        [cell setFont: [[NSFontManager sharedFontManager] convertFont:fontToUse toNotHaveTrait:NSBoldFontMask]];
1411 1412
}

1413 1414
- (id)playingItem
{
1415
    playlist_t *p_playlist = pl_Get(VLCIntf);
1416

1417
    id o_playing_item;
1418 1419

    PL_LOCK;
1420
    o_playing_item = [o_outline_dict objectForKey: [NSString stringWithFormat:@"%p",  playlist_CurrentPlayingItem(p_playlist)]];
1421 1422
    PL_UNLOCK;

1423 1424
    return o_playing_item;
}
1425 1426 1427 1428 1429

- (NSArray *)draggedItems
{
    return [[o_nodes_array arrayByAddingObjectsFromArray: o_items_array] retain];
}
1430

1431
- (void)setColumn: (NSString *)o_column state: (NSInteger)i_state translationDict:(NSDictionary *)o_dict
1432 1433 1434
{
    NSTableColumn * o_work_tc;

1435
    if (i_state == NSOnState) {
1436 1437 1438 1439
        NSString *o_title = [o_dict objectForKey:o_column];
        if (!o_title)
            return;

1440 1441 1442 1443
        o_work_tc = [[NSTableColumn alloc] initWithIdentifier: o_column];
        [o_work_tc setEditable: NO];
        [[o_work_tc dataCell] setFont: [NSFont controlContentFontOfSize:11.]];

1444 1445
        [[o_work_tc headerCell] setStringValue: [o_dict objectForKey:o_column]];

1446
        if ([o_column isEqualToString: TRACKNUM_COLUMN]) {
1447 1448
            [o_work_tc setWidth: 20.];
            [o_work_tc setResizingMask: NSTableColumnNoResizing];
1449 1450 1451 1452 1453 1454 1455 1456 1457 1458
            [[o_work_tc headerCell] setStringValue: @"#"];
        }

        [o_outline_view addTableColumn: o_work_tc];
        [o_work_tc release];
        [o_outline_view reloadData];
        [o_outline_view setNeedsDisplay: YES];
    }
    else
        [o_outline_view removeTableColumn: [o_outline_view tableColumnWithIdentifier: o_column]];
1459 1460

    [o_outline_view setOutlineTableColumn: [o_outline_view tableColumnWithIdentifier:TITLE_COLUMN]];
1461 1462 1463 1464 1465 1466 1467 1468
}

- (void)saveTableColumns
{
    NSMutableArray * o_arrayToSave = [[NSMutableArray alloc] init];
    NSArray * o_columns = [[NSArray alloc] initWithArray:[o_outline_view tableColumns]];
    NSUInteger count = [o_columns count];
    NSTableColumn * o_currentColumn;
1469
    for (NSUInteger i = 0; i < count; i++) {
1470
        o_currentColumn = [o_columns objectAtIndex:i];
1471
        [o_arrayToSave addObject:[NSArray arrayWithObjects:[o_currentColumn identifier], [NSNumber numberWithFloat:[o_currentColumn width]], nil]];
1472 1473 1474 1475 1476 1477 1478
    }
    [[NSUserDefaults standardUserDefaults] setObject: o_arrayToSave forKey:@"PlaylistColumnSelection"];
    [[NSUserDefaults standardUserDefaults] synchronize];
    [o_columns release];
    [o_arrayToSave release];
}

1479
- (void)continuePlaybackWhereYouLeftOff:(input_thread_t *)p_input_thread
1480 1481 1482
{
    NSDictionary *recentlyPlayedFiles = [[NSUserDefaults standardUserDefaults] objectForKey:@"recentlyPlayedMedia"];
    if (recentlyPlayedFiles) {
1483 1484 1485 1486
        input_item_t *p_item = input_GetItem(p_input_thread);
        if (!p_item)
            return;

1487 1488 1489 1490 1491 1492 1493 1494
        /* allow the user to over-write the start-time */
        if (p_item->i_options > 0) {
            for (int x = 0; x < p_item->i_options; x++) {
                if (strstr(p_item->ppsz_options[x],"start-time"))
                    return;
            }
        }

1495
        char *psz_url = decode_URI(input_item_GetURI(p_item));
1496 1497 1498 1499
        NSString *url = [NSString stringWithUTF8String:psz_url ? psz_url : ""];
        free(psz_url);

        NSNumber *lastPosition = [recentlyPlayedFiles objectForKey:url];
1500
        if (lastPosition && lastPosition.intValue > 0) {
1501 1502 1503 1504 1505 1506
            vlc_value_t pos;
            var_Get(p_input_thread, "position", &pos);
            float f_current_pos = 100. * pos.f_float;
            long long int dur = input_item_GetDuration(p_item) / 1000000;
            int current_pos_in_sec = (f_current_pos * dur) / 100;

1507
            if (current_pos_in_sec >= lastPosition.intValue)
1508
                return;
1509 1510

            int settingValue = config_GetInt(VLCIntf, "macosx-continue-playback");
1511
            NSInteger returnValue = NSAlertErrorReturn;
1512 1513

            if (settingValue == 0) {
1514
                NSAlert *theAlert = [NSAlert alertWithMessageText:_NS("Continue playback?") defaultButton:_NS("Continue") alternateButton:_NS("Restart playback") otherButton:_NS("Always continue") informativeTextWithFormat:_NS("Playback of \"%@\" will continue at %@"), [NSString stringWithUTF8String:input_item_GetTitleFbName(p_item)], [[VLCStringUtility sharedInstance] stringForTime:lastPosition.intValue]];
1515

1516
                [[VLCCoreInteraction sharedInstance] pause];
1517
                returnValue = [theAlert runModal];
1518
                [[VLCCoreInteraction sharedInstance] playOrPause];
1519 1520
            }

1521 1522
            if (returnValue == NSAlertAlternateReturn || settingValue == 2)
                lastPosition = [NSNumber numberWithInt:0];
1523 1524 1525 1526

            pos.f_float = (float)lastPosition.intValue / (float)dur;
            msg_Dbg(VLCIntf, "continuing playback at %2.2f", pos.f_float);
            var_Set(p_input_thread, "position", pos);
1527

1528
            if (returnValue == NSAlertOtherReturn)
1529 1530 1531 1532 1533
                config_PutInt(VLCIntf, "macosx-continue-playback", 1);
        }
    }
}

1534 1535 1536 1537 1538 1539 1540 1541 1542
- (void)storePlaybackPositionForItem:(input_thread_t *)p_input_thread
{
    input_item_t *p_item = input_GetItem(p_input_thread);
    if (!p_item)
        return;

    char *psz_url = decode_URI(input_item_GetURI(p_item));
    NSString *url = [NSString stringWithUTF8String:psz_url ? psz_url : ""];
    free(psz_url);
1543 1544 1545 1546 1547 1548 1549 1550 1551 1552

    if (url.length < 1)
        return;

    if (![[NSURL URLWithString:url] isFileURL])
        return;

    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    NSMutableDictionary *mutDict = [[NSMutableDictionary alloc] initWithDictionary:[defaults objectForKey:@"recentlyPlayedMedia"]];

1553 1554 1555 1556 1557
    vlc_value_t pos;
    var_Get(p_input_thread, "position", &pos);
    float f_current_pos = 100. * pos.f_float;
    long long int dur = input_item_GetDuration(p_item) / 1000000;
    int current_pos_in_sec = (f_current_pos * dur) / 100;
1558
    NSMutableArray *mediaList = [[defaults objectForKey:@"recentlyPlayedMediaList"] mutableCopy];
1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578

    if (pos.f_float > .05 && pos.f_float < .95 && dur > 180) {
        [mutDict setObject:[NSNumber numberWithInt:current_pos_in_sec] forKey:url];

        [mediaList removeObject:url];
        [mediaList addObject:url];
        NSUInteger mediaListCount = mediaList.count;
        if (mediaListCount > 30) {
            for (NSUInteger x = 0; x < mediaListCount - 30; x++) {
                [mutDict removeObjectForKey:[mediaList objectAtIndex:0]];
                [mediaList removeObjectAtIndex:0];
            }
        }
    } else {
        [mutDict removeObjectForKey:url];
        [mediaList removeObject:url];
    }
    [defaults setObject:mutDict forKey:@"recentlyPlayedMedia"];
    [defaults setObject:mediaList forKey:@"recentlyPlayedMediaList"];
    [defaults synchronize];
1579 1580 1581

    [mutDict release];
    [mediaList release];
1582 1583
}

1584
@end
1585

1586
@implementation VLCPlaylist (NSOutlineViewDataSource)
1587

1588
- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item
Benjamin Pracht's avatar
Benjamin Pracht committed
1589
{
1590
    id o_value = [super outlineView: outlineView child: index ofItem: item];
1591

1592
    [o_outline_dict setObject:o_value forKey:[NSString stringWithFormat:@"%p", [o_value pointerValue]]];
1593
    return o_value;
1594 1595
}

1596 1597
/* Required for drag & drop and reordering */
- (BOOL)outlineView:(NSOutlineView *)outlineView writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pboard
1598
{
1599
    playlist_t *p_playlist = pl_Get(VLCIntf);
1600

1601 1602
    /* First remove the items that were moved during the last drag & drop
       operation */
Benjamin Pracht's avatar
Benjamin Pracht committed
1603 1604
    [o_items_array removeAllObjects];
    [o_nodes_array removeAllObjects];
1605

1606 1607
    NSUInteger itemCount = [items count];

1608
    for (NSUInteger i = 0 ; i < itemCount ; i++) {
1609
        id o_item = [items objectAtIndex:i];
1610

1611
        /* Fill the items and nodes to move in 2 different arrays */
1612
        if (((playlist_item_t *)[o_item pointerValue])->i_children > 0)
1613 1614 1615 1616 1617 1618 1619
            [o_nodes_array addObject: o_item];
        else
            [o_items_array addObject: o_item];
    }

    /* Now we need to check if there are selected items that are in already
       selected nodes. In that case, we only want to move the nodes */
1620 1621 1622 1623
    [self removeItemsFrom: o_nodes_array ifChildrenOf: o_nodes_array];
    [self removeItemsFrom: o_items_array ifChildrenOf: o_nodes_array];

    /* We add the "VLCPlaylistItemPboardType" type to be able to recognize
Benjamin Pracht's avatar
Benjamin Pracht committed
1624
       a Drop operation coming from the playlist. */
1625

1626
    [pboard declareTypes: [NSArray arrayWithObject:@"VLCPlaylistItemPboardType"] owner: self];
1627
    [pboard setData:[NSData data] forType:@"VLCPlaylistItemPboardType"];
1628 1629

    return YES;
1630 1631
}

1632
- (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id <NSDraggingInfo>)info proposedItem:(id)item proposedChildIndex:(NSInteger)index
1633
{
1634
    playlist_t *p_playlist = pl_Get(VLCIntf);
1635 1636
    NSPasteboard *o_pasteboard = [info draggingPasteboard];

1637
    if (!p_playlist) return NSDragOperationNone;
Benjamin Pracht's avatar
Benjamin Pracht committed
1638

1639
    /* Dropping ON items is not allowed if item is not a node */
1640 1641 1642
    if (item) {
        if (index == NSOutlineViewDropOnItemIndex &&
                ((playlist_item_t *)[item pointerValue])->i_children == -1) {
1643 1644
            return NSDragOperationNone;
        }
1645
    }
1646

1647 1648
    /* We refuse to drop an item in anything else than a child of the General
       Node. We still accept items that would be root nodes of the outlineview
1649
       however, to allow drop in an empty playlist. */
1650 1651
    if (!(([self isItem: [item pointerValue] inNode: p_playlist->p_local_category checkItemExistence: NO locked: NO] ||
            (var_CreateGetBool(p_playlist, "media-library") && [self isItem: [item pointerValue] inNode: p_playlist->p_ml_category checkItemExistence: NO locked: NO])) || item == nil)) {
1652 1653
        return NSDragOperationNone;
    }
Benjamin Pracht's avatar
Benjamin Pracht committed
1654

1655
    /* Drop from the Playlist */
1656
    if ([[o_pasteboard types] containsObject: @"VLCPlaylistItemPboardType"]) {
1657
        NSUInteger count = [o_nodes_array count];
1658
        for (NSUInteger i = 0 ; i < count ; i++) {
1659
            /* We refuse to Drop in a child of an item we are moving */
1660
            if ([self isItem: [item pointerValue] inNode: [[o_nodes_array objectAtIndex:i] pointerValue] checkItemExistence: NO locked:NO]) {
Benjamin Pracht's avatar
Benjamin Pracht committed
1661 1662 1663
                return NSDragOperationNone;
            }
        }
1664 1665 1666
        return NSDragOperationMove;
    }
    /* Drop from the Finder */
1667
    else if ([[o_pasteboard types] containsObject: NSFilenamesPboardType]) {
1668 1669
        return NSDragOperationGeneric;
    }
1670 1671 1672
    return NSDragOperationNone;
}

1673
- (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id <NSDraggingInfo>)info item:(id)item childIndex:(NSInteger)index
1674
{
1675
    playlist_t * p_playlist =  pl_Get(VLCIntf);
1676 1677
    NSPasteboard *o_pasteboard = [info draggingPasteboard];

1678
    /* Drag & Drop inside the playlist */
1679
    if ([[o_pasteboard types] containsObject: @"VLCPlaylistItemPboardType"]) {
1680 1681 1682
        if (index == -1) // this is no valid target, sanitize to top of table
            index = 0;

1683
        int i_row = 0;
1684
        playlist_item_t *p_new_parent, *p_item = NULL;
1685
        NSArray *o_all_items = [o_nodes_array arrayByAddingObjectsFromArray: o_items_array];
1686
        /* If the item is to be dropped as root item of the outline, make it a
1687
           child of the respective general node, if is either the pl or the ml
1688
           Else, choose the proposed parent as parent. */
1689
        if (item == nil) {
1690
            if ([self currentPlaylistRoot] == p_playlist->p_local_category || [self currentPlaylistRoot] == p_playlist->p_ml_category)
1691 1692
                p_new_parent = [self currentPlaylistRoot];
            else
1693
                return NO;
1694 1695 1696
        }
        else
            p_new_parent = [item pointerValue];
1697

Benjamin Pracht's avatar
 
Benjamin Pracht committed
1698 1699
        /* Make sure the proposed parent is a node.
           (This should never be true) */
1700
        if (p_new_parent->i_children < 0)
1701
            return NO;
1702

1703
        NSUInteger count = [o_all_items count];
1704
        if (count == 0)
1705 1706
            return NO;

1707 1708
        playlist_item_t **pp_items = (playlist_item_t **)calloc(count, sizeof(playlist_item_t*));
        if (!pp_items)
1709 1710 1711 1712
            return NO;

        PL_LOCK;
        NSUInteger j = 0;
1713
        for (NSUInteger i = 0; i < count; i++) {
1714
            p_item = [[o_all_items objectAtIndex:i] pointerValue];
1715
            if (p_item)
1716 1717
                pp_items[j++] = p_item;
        }
1718

1719
        if (j == 0 || playlist_TreeMoveMany(p_playlist, j, pp_items, p_new_parent, index) != VLC_SUCCESS) {
1720
            PL_UNLOCK;
1721
            free(pp_items);
1722
            return NO;
1723
        }
1724 1725

        PL_UNLOCK;
1726
        free(pp_items);
1727

1728
        [self playlistUpdated];
1729
        i_row = [o_outline_view rowForItem:[o_outline_dict objectForKey:[NSString stringWithFormat: @"%p", [[o_all_items objectAtIndex:0] pointerValue]]]];
1730

1731
        if (i_row == -1)
1732
            i_row = [o_outline_view rowForItem:[o_outline_dict objectForKey:[NSString stringWithFormat: @"%p", p_new_parent]]];
1733 1734

        [o_outline_view deselectAll: self];
1735
        [o_outline_view selectRowIndexes:[NSIndexSet indexSetWithIndex:i_row] byExtendingSelection:NO];
1736 1737 1738 1739 1740
        [o_outline_view scrollRowToVisible: i_row];

        return YES;
    }

1741
    else if ([[o_pasteboard types] containsObject: NSFilenamesPboardType]) {
1742
        if ([self currentPlaylistRoot] != p_playlist->p_local_category && [self currentPlaylistRoot] != p_playlist->p_ml_category)
1743 1744
            return NO;

1745 1746
        playlist_item_t *p_node = [item pointerValue];

1747 1748 1749 1750
        NSArray *o_values = [[o_pasteboard propertyListForType: NSFilenamesPboardType]
                                sortedArrayUsingSelector: @selector(caseInsensitiveCompare:)];
        NSUInteger count = [o_values count];
        NSMutableArray *o_array = [NSMutableArray arrayWithCapacity:count];
1751
        input_thread_t * p_input = pl_CurrentInput(VLCIntf);
1752

1753
        if (count == 1 && p_input) {
1754
            int i_result = input_AddSubtitleOSD(p_input, vlc_path2uri([[o_values objectAtIndex:0] UTF8String], NULL), true, true);
1755
            vlc_object_release(p_input);
1756
            if (i_result == VLC_SUCCESS)
1757 1758
                return YES;
        }
1759 1760
        else if (p_input)
            vlc_object_release(p_input);
1761

1762
        for (NSUInteger i = 0; i < count; i++) {
1763
            NSDictionary *o_dic;
1764
            char *psz_uri = vlc_path2uri([[o_values objectAtIndex:i] UTF8String], NULL);
1765
            if (!psz_uri)
1766 1767 1768 1769
                continue;

            o_dic = [NSDictionary dictionaryWithObject:[NSString stringWithCString:psz_uri encoding:NSUTF8StringEncoding] forKey:@"ITEM_URL"];

1770
            free(psz_uri);
1771

1772
            [o_array addObject: o_dic];
1773 1774
        }

1775
        if (item == nil)
1776
            [self appendArray:o_array atPos:index enqueue: YES];
1777 1778
        else {
            assert(p_node->i_children != -1);
1779
            [self appendNodeArray:o_array inNode: p_node atPos:index enqueue:YES];
1780 1781 1782 1783 1784
        }
        return YES;
    }
    return NO;
}
1785

1786
@end