VLCPlaylist.m 33.3 KB
Newer Older
1
/*****************************************************************************
2
 * VLCPlaylist.m: MacOS X interface module
3
 *****************************************************************************
4
* Copyright (C) 2002-2015 VLC authors and VideoLAN
Benjamin Pracht's avatar
...  
Benjamin Pracht committed
5
 * $Id$
6
 *
7 8
 * Authors: Derk-Jan Hartman <hartman at videola/n dot org>
 *          Benjamin Pracht <bigben at videolan dot org>
9
 *          Felix Paul Kühne <fkuehne at videolan dot org>
10
 *          David Fuhrmann <david dot fuhrmann at googlemail dot com>
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
#import "VLCPlaylist.h"
David Fuhrmann's avatar
David Fuhrmann committed
46
#import "VLCMainMenu.h"
47
#import "VLCPlaylistInfo.h"
48
#import "VLCResumeDialogController.h"
49
#import "VLCOpenWindowController.h"
50

Pierre's avatar
Pierre committed
51 52
#include <vlc_keys.h>
#import <vlc_interface.h>
53 54
#include <vlc_url.h>

55
/*****************************************************************************
56 57 58 59 60
 * 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.
61 62 63 64 65 66 67
 *****************************************************************************/

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

68 69
@interface VLCPlaylist ()
{
70 71
    NSImage *_descendingSortingImage;
    NSImage *_ascendingSortingImage;
72 73 74

    BOOL b_selected_item_met;
    BOOL b_isSortDescending;
75
    NSTableColumn *_sortTableColumn;
76 77

    BOOL b_playlistmenu_nib_loaded;
78

79
    PLModel *_model;
80 81 82 83 84

    // information for playlist table columns menu

    NSDictionary *_translationsForPlaylistTableColumns;
    NSArray *_menuOrderOfPlaylistTableColumns;
85 86
}

87 88 89
- (void)saveTableColumns;
@end

90 91
@implementation VLCPlaylist

92 93 94 95 96 97 98 99 100 101 102 103
- (id)init
{
    self = [super init];
    if (self) {
        /* 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. */
        _ascendingSortingImage = [[NSOutlineView class] _defaultTableHeaderSortImage];
        _descendingSortingImage = [[NSOutlineView class] _defaultTableHeaderReverseSortImage];

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

104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123

        _translationsForPlaylistTableColumns = [[NSDictionary alloc] initWithObjectsAndKeys:
                                                _NS("Track Number"),  TRACKNUM_COLUMN,
                                                _NS("Title"),         TITLE_COLUMN,
                                                _NS("Author"),        ARTIST_COLUMN,
                                                _NS("Duration"),      DURATION_COLUMN,
                                                _NS("Genre"),         GENRE_COLUMN,
                                                _NS("Album"),         ALBUM_COLUMN,
                                                _NS("Description"),   DESCRIPTION_COLUMN,
                                                _NS("Date"),          DATE_COLUMN,
                                                _NS("Language"),      LANGUAGE_COLUMN,
                                                _NS("URI"),           URI_COLUMN,
                                                _NS("File Size"),     FILESIZE_COLUMN,
                                                nil];
        // this array also assigns tags (index) to type of menu item
        _menuOrderOfPlaylistTableColumns = [[NSArray alloc] initWithObjects: TRACKNUM_COLUMN, TITLE_COLUMN,
                                            ARTIST_COLUMN, DURATION_COLUMN, GENRE_COLUMN, ALBUM_COLUMN,
                                            DESCRIPTION_COLUMN, DATE_COLUMN, LANGUAGE_COLUMN, URI_COLUMN,
                                            FILESIZE_COLUMN,nil];

124 125 126 127
    }
    return self;
}

David Fuhrmann's avatar
David Fuhrmann committed
128 129
+ (void)initialize
{
130
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
131 132 133 134
    NSMutableArray *columnArray = [[NSMutableArray alloc] init];
    [columnArray addObject: [NSArray arrayWithObjects:TITLE_COLUMN, [NSNumber numberWithFloat:190.], nil]];
    [columnArray addObject: [NSArray arrayWithObjects:ARTIST_COLUMN, [NSNumber numberWithFloat:95.], nil]];
    [columnArray addObject: [NSArray arrayWithObjects:DURATION_COLUMN, [NSNumber numberWithFloat:95.], nil]];
135 136

    NSDictionary *appDefaults = [NSDictionary dictionaryWithObjectsAndKeys:
137
                                 [NSArray arrayWithArray:columnArray], @"PlaylistColumnSelection",
138 139
                                 [NSArray array], @"recentlyPlayedMediaList",
                                 [NSDictionary dictionary], @"recentlyPlayedMedia", nil];
140 141 142 143

    [defaults registerDefaults:appDefaults];
}

144
- (PLModel *)model
145
{
146
    return _model;
147 148 149 150 151 152
}

- (void)reloadStyles
{
    NSFont *fontToUse;
    CGFloat rowHeight;
153
    if (var_InheritBool(getIntf(), "macosx-large-text")) {
154 155 156 157 158 159 160
        fontToUse = [NSFont systemFontOfSize:13.];
        rowHeight = 21.;
    } else {
        fontToUse = [NSFont systemFontOfSize:11.];
        rowHeight = 16.;
    }

161
    NSArray *columns = [_outlineView tableColumns];
162 163
    NSUInteger count = columns.count;
    for (NSUInteger x = 0; x < count; x++)
164
        [[columns[x] dataCell] setFont:fontToUse];
165
    [_outlineView setRowHeight:rowHeight];
166 167
}

168 169
- (void)awakeFromNib
{
170
    // This is only called for the playlist popup menu
David Fuhrmann's avatar
David Fuhrmann committed
171
    [self initStrings];
172 173
}

174 175 176 177 178
- (void)setOutlineView:(VLCPlaylistView * __nullable)outlineView
{
    _outlineView = outlineView;
    [_outlineView setDelegate:self];

179
    playlist_t * p_playlist = pl_Get(getIntf());
180

181
    _model = [[PLModel alloc] initWithOutlineView:_outlineView playlist:p_playlist rootItem:p_playlist->p_playing];
182 183 184 185 186 187 188 189 190
    [_outlineView setDataSource:_model];
    [_outlineView reloadData];

    [_outlineView setTarget: self];
    [_outlineView setDoubleAction: @selector(playItem:)];

    [_outlineView setAllowsEmptySelection: NO];
    [_outlineView registerForDraggedTypes: [NSArray arrayWithObjects:NSFilenamesPboardType, @"VLCPlaylistItemPboardType", nil]];
    [_outlineView setIntercellSpacing: NSMakeSize (0.0, 1.0)];
191 192

    [self reloadStyles];
193 194
}

195 196 197 198
- (void)setPlaylistHeaderView:(NSTableHeaderView * __nullable)playlistHeaderView
{
    VLCMainMenu *mainMenu = [[VLCMain sharedInstance] mainMenu];
    _playlistHeaderView = playlistHeaderView;
199 200 201 202

    // Setup playlist table column selection for both context and main menu
    NSMenu *contextMenu = [[NSMenu alloc] init];
    [self setupPlaylistTableColumnsForMenu:contextMenu];
203
    [_playlistHeaderView setMenu: contextMenu];
204
    [self setupPlaylistTableColumnsForMenu:[[[VLCMain sharedInstance] mainMenu] playlistTableColumnsMenu]];
205

206 207 208
    NSArray * columnArray = [[NSUserDefaults standardUserDefaults] arrayForKey:@"PlaylistColumnSelection"];
    NSUInteger columnCount = [columnArray count];
    NSString * column;
209

210 211 212
    for (NSUInteger i = 0; i < columnCount; i++) {
        column = [columnArray[i] firstObject];
        if ([column isEqualToString:@"status"])
213 214
            continue;

215
        if(![self setPlaylistColumnTableState: NSOnState forColumn:column])
216 217
            continue;

218
        [[_outlineView tableColumnWithIdentifier: column] setWidth: [columnArray[i][1] floatValue]];
219 220 221 222 223 224 225
    }
}

- (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];
226
}
227

228 229
- (void)initStrings
{
230 231 232 233 234 235 236 237 238 239
    [_playPlaylistMenuItem setTitle: _NS("Play")];
    [_deletePlaylistMenuItem setTitle: _NS("Delete")];
    [_recursiveExpandPlaylistMenuItem setTitle: _NS("Expand Node")];
    [_selectAllPlaylistMenuItem setTitle: _NS("Select All")];
    [_infoPlaylistMenuItem setTitle: _NS("Media Information...")];
    [_downloadCoverArtPlaylistMenuItem setTitle: _NS("Download Cover Art")];
    [_preparsePlaylistMenuItem setTitle: _NS("Fetch Meta Data")];
    [_revealInFinderPlaylistMenuItem setTitle: _NS("Reveal in Finder")];
    [_sortNamePlaylistMenuItem setTitle: _NS("Sort Node by Name")];
    [_sortAuthorPlaylistMenuItem setTitle: _NS("Sort Node by Author")];
240
    [_addFilesToPlaylistMenuItem setTitle: _NS("Add File...")];
241 242
}

243 244
- (void)playlistUpdated
{
245
    [_outlineView reloadData];
246
}
247

248 249
- (void)playbackModeUpdated
{
250
    [_model playbackModeUpdated];
251 252
}

253 254 255

- (BOOL)isSelectionEmpty
{
256
    return [_outlineView selectedRow] == -1;
257 258
}

259
- (void)currentlyPlayingItemChanged
260
{
261 262 263 264 265
    PLItem *item = [[self model] currentlyPlayingItem];
    if (!item)
        return;

    // select item
266
    NSInteger itemIndex = [_outlineView rowForItem:item];
267 268 269 270 271
    if (itemIndex < 0) {
        // expand if needed
        while (item != nil) {
            PLItem *parent = [item parent];

272
            if (![_outlineView isExpandable: parent])
273
                break;
274 275
            if (![_outlineView isItemExpanded: parent])
                [_outlineView expandItem: parent];
276 277 278 279
            item = parent;
        }

        // search for row again
280
        itemIndex = [_outlineView rowForItem:item];
281 282 283 284 285
        if (itemIndex < 0) {
            return;
        }
    }

286
    [_outlineView selectRowIndexes: [NSIndexSet indexSetWithIndex: itemIndex] byExtendingSelection: NO];
287
    [_outlineView scrollRowToVisible: itemIndex];
288 289
}

290 291 292
#pragma mark -
#pragma mark Playlist actions

Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
293
/* When called retrieves the selected outlineview row and plays that node or item */
294 295
- (IBAction)playItem:(id)sender
{
296
    playlist_t *p_playlist = pl_Get(getIntf());
Benjamin Pracht's avatar
 
Benjamin Pracht committed
297

298
    // ignore clicks on column header when handling double action
299
    if (sender == _outlineView && [_outlineView clickedRow] == -1)
300 301
        return;

302
    PLItem *o_item = [_outlineView itemAtRow:[_outlineView selectedRow]];
303 304
    if (!o_item)
        return;
305

306 307 308
    PL_LOCK;
    playlist_item_t *p_item = playlist_ItemGetById(p_playlist, [o_item plItemId]);
    playlist_item_t *p_node = playlist_ItemGetById(p_playlist, [[[self model] rootItem] plItemId]);
309

310
    if (p_item && p_node) {
311
        playlist_Control(p_playlist, PLAYLIST_VIEWPLAY, pl_Locked, p_node, p_item);
312
    }
313
    PL_UNLOCK;
314
}
315

316 317
- (IBAction)revealItemInFinder:(id)sender
{
318
    NSIndexSet *selectedRows = [_outlineView selectedRowIndexes];
319 320
    if (selectedRows.count < 1)
        return;
321

322
    PLItem *o_item = [_outlineView itemAtRow:selectedRows.firstIndex];
323

324 325 326 327 328 329 330
    char *psz_url = input_item_GetURI([o_item input]);
    if (!psz_url)
        return;
    char *psz_path = vlc_uri2path(psz_url);
    NSString *path = toNSStr(psz_path);
    free(psz_url);
    free(psz_path);
331

332
    msg_Dbg(getIntf(), "Reveal url %s in finder", [path UTF8String]);
333
    [[NSWorkspace sharedWorkspace] selectFile: path inFileViewerRootedAtPath: path];
334
}
335

336 337 338 339
/* When called retrieves the selected outlineview row and plays that node or item */
- (IBAction)preparseItem:(id)sender
{
    int i_count;
340
    NSIndexSet *o_selected_indexes;
341
    intf_thread_t * p_intf = getIntf();
342
    playlist_t * p_playlist = pl_Get(p_intf);
343 344
    playlist_item_t *p_item = NULL;

345
    o_selected_indexes = [_outlineView selectedRowIndexes];
346 347 348 349
    i_count = [o_selected_indexes count];

    NSUInteger indexes[i_count];
    [o_selected_indexes getIndexes:indexes maxCount:i_count inIndexRange:nil];
350
    for (int i = 0; i < i_count; i++) {
351 352
        PLItem *o_item = [_outlineView itemAtRow:indexes[i]];
        [_outlineView deselectRow: indexes[i]];
353

354 355 356
        if (![o_item isLeaf]) {
            msg_Dbg(p_intf, "preparsing nodes not implemented");
            continue;
357
        }
358

359
        libvlc_MetadataRequest(p_intf->obj.libvlc, [o_item input], META_REQUEST_OPTION_NONE, -1, NULL);
360

361 362 363 364
    }
    [self playlistUpdated];
}

365 366 367
- (IBAction)downloadCoverArt:(id)sender
{
    int i_count;
368
    NSIndexSet *o_selected_indexes;
369
    intf_thread_t * p_intf = getIntf();
370
    playlist_t * p_playlist = pl_Get(p_intf);
371 372
    playlist_item_t *p_item = NULL;

373
    o_selected_indexes = [_outlineView selectedRowIndexes];
374 375 376 377
    i_count = [o_selected_indexes count];

    NSUInteger indexes[i_count];
    [o_selected_indexes getIndexes:indexes maxCount:i_count inIndexRange:nil];
378
    for (int i = 0; i < i_count; i++) {
379
        PLItem *o_item = [_outlineView itemAtRow: indexes[i]];
380

381 382 383
        if (![o_item isLeaf])
            continue;

384
        libvlc_ArtRequest(p_intf->obj.libvlc, [o_item input], META_REQUEST_OPTION_NONE);
385 386 387 388
    }
    [self playlistUpdated];
}

Benjamin Pracht's avatar
Benjamin Pracht committed
389 390
- (IBAction)selectAll:(id)sender
{
391
    [_outlineView selectAll: nil];
Benjamin Pracht's avatar
Benjamin Pracht committed
392 393
}

394 395
- (IBAction)showInfoPanel:(id)sender
{
396
    [[[VLCMain sharedInstance] currentMediaInfoPanel] toggleWindow:sender];
397 398
}

399 400 401 402 403
- (IBAction)addFilesToPlaylist:(id)sender
{
    [[[VLCMain sharedInstance] open] openFile];
}

Benjamin Pracht's avatar
Benjamin Pracht committed
404 405
- (IBAction)deleteItem:(id)sender
{
406
    [_model deleteSelectedItem];
Benjamin Pracht's avatar
Benjamin Pracht committed
407
}
Benjamin Pracht's avatar
Benjamin Pracht committed
408

409 410 411 412 413 414 415
- (IBAction)sortNodeByName:(id)sender
{
    [self sortNode: SORT_TITLE];
}

- (IBAction)sortNodeByAuthor:(id)sender
{
Clément Stenac's avatar
Clément Stenac committed
416
    [self sortNode: SORT_ARTIST];
417 418 419 420
}

- (void)sortNode:(int)i_mode
{
421
    playlist_t * p_playlist = pl_Get(getIntf());
422 423
    playlist_item_t * p_item;

424 425
    // TODO why do we need this kind of sort? It looks crap and confusing...

426 427
//    if ([_outlineView selectedRow] > -1) {
//        p_item = [[_outlineView itemAtRow: [_outlineView selectedRow]] pointerValue];
428 429 430 431 432 433 434 435 436 437 438 439 440
//        if (!p_item)
//            return;
//    } else
//        p_item = [self currentPlaylistRoot]; // If no item is selected, sort the whole playlist
//
//    PL_LOCK;
//    if (p_item->i_children > -1) // the item is a node
//        playlist_RecursiveNodeSort(p_playlist, p_item, i_mode, ORDER_NORMAL);
//    else
//        playlist_RecursiveNodeSort(p_playlist, p_item->p_parent, i_mode, ORDER_NORMAL);
//
//    PL_UNLOCK;
//    [self playlistUpdated];
441 442
}

443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479
// Actions for playlist column selections


- (void)togglePlaylistColumnTable:(id)sender
{
    NSInteger i_new_state = ![sender state];
    NSInteger i_tag = [sender tag];

    NSString *column = [_menuOrderOfPlaylistTableColumns objectAtIndex:i_tag];

    [self setPlaylistColumnTableState:i_new_state forColumn:column];
}

- (BOOL)setPlaylistColumnTableState:(NSInteger)i_state forColumn:(NSString *)columnId
{
    NSUInteger i_tag = [_menuOrderOfPlaylistTableColumns indexOfObject: columnId];
    // prevent setting unknown columns
    if(i_tag == NSNotFound)
        return NO;

    // update state of menu items
    [[[_playlistHeaderView menu] itemWithTag: i_tag] setState: i_state];
    [[[[[VLCMain sharedInstance] mainMenu] playlistTableColumnsMenu] itemWithTag: i_tag] setState: i_state];

    // Change outline view
    if (i_state == NSOnState) {
        NSString *title = [_translationsForPlaylistTableColumns objectForKey:columnId];
        if (!title)
            return NO;

        NSTableColumn *tableColumn = [[NSTableColumn alloc] initWithIdentifier:columnId];
        [tableColumn setEditable:NO];
        [[tableColumn dataCell] setFont:[NSFont controlContentFontOfSize:11.]];

        [[tableColumn headerCell] setStringValue:[_translationsForPlaylistTableColumns objectForKey:columnId]];

        if ([columnId isEqualToString: TRACKNUM_COLUMN]) {
480 481
            [tableColumn setMinWidth:20.];
            [tableColumn setMaxWidth:70.];
482
            [[tableColumn headerCell] setStringValue:@"#"];
483 484 485

        } else {
            [tableColumn setMinWidth:42.];
486 487 488 489 490 491 492 493 494 495 496 497 498 499
        }

        [_outlineView addTableColumn:tableColumn];
        [_outlineView reloadData];
        [_outlineView setNeedsDisplay: YES];
    }
    else
        [_outlineView removeTableColumn: [_outlineView tableColumnWithIdentifier:columnId]];

    [_outlineView setOutlineTableColumn: [_outlineView tableColumnWithIdentifier:TITLE_COLUMN]];

    return YES;
}

500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516
- (BOOL)validateMenuItem:(NSMenuItem *)item
{
    if ([item action] == @selector(revealItemInFinder:)) {
        NSIndexSet *selectedRows = [_outlineView selectedRowIndexes];
        if (selectedRows.count != 1)
            return NO;

        PLItem *o_item = [_outlineView itemAtRow:selectedRows.firstIndex];

        // Check if item exists in file system
        char *psz_url = input_item_GetURI([o_item input]);
        NSURL *url = [NSURL URLWithString:toNSStr(psz_url)];
        free(psz_url);
        if (![url isFileURL])
            return NO;
        if (![[NSFileManager defaultManager] fileExistsAtPath:[url path]])
            return NO;
517 518 519 520 521 522 523

    } else if ([item action] == @selector(deleteItem:)) {
        return [_outlineView numberOfSelectedRows] > 0 && _model.editAllowed;
    } else if ([item action] == @selector(selectAll:)) {
        return [_outlineView numberOfRows] >= 0;
    } else if ([item action] == @selector(playItem:)) {
        return [_outlineView numberOfSelectedRows] > 0;
524 525 526 527 528
    }

    return YES;
}

529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565
#pragma mark -
#pragma mark Helper for playlist table columns

- (void)setupPlaylistTableColumnsForMenu:(NSMenu *)menu
{
    NSMenuItem *menuItem;
    NSUInteger count = [_menuOrderOfPlaylistTableColumns count];
    for (NSUInteger i = 0; i < count; i++) {
        NSString *columnId = [_menuOrderOfPlaylistTableColumns objectAtIndex:i];
        NSString *title = [_translationsForPlaylistTableColumns objectForKey:columnId];
        menuItem = [menu addItemWithTitle:title
                                   action:@selector(togglePlaylistColumnTable:)
                            keyEquivalent:@""];
        [menuItem setTarget:self];
        [menuItem setTag:i];

        /* don't set a valid action for the title column selector, since we want it to be disabled */
        if ([columnId isEqualToString: TITLE_COLUMN])
            [menuItem setAction:nil];

    }
}

- (void)saveTableColumns
{
    NSMutableArray *arrayToSave = [[NSMutableArray alloc] init];
    NSArray *columns = [[NSArray alloc] initWithArray:[_outlineView tableColumns]];
    NSUInteger columnCount = [columns count];
    NSTableColumn *currentColumn;
    for (NSUInteger i = 0; i < columnCount; i++) {
        currentColumn = columns[i];
        [arrayToSave addObject:[NSArray arrayWithObjects:[currentColumn identifier], [NSNumber numberWithFloat:[currentColumn width]], nil]];
    }
    [[NSUserDefaults standardUserDefaults] setObject:arrayToSave forKey:@"PlaylistColumnSelection"];
    [[NSUserDefaults standardUserDefaults] synchronize];
}

566 567 568
#pragma mark -
#pragma mark Item helpers

569
- (input_item_t *)createItem:(NSDictionary *)itemToCreateDict
570
{
571
    intf_thread_t *p_intf = getIntf();
572
    playlist_t *p_playlist = pl_Get(p_intf);
573

574
    input_item_t *p_input;
575
    BOOL b_rem = FALSE, b_dir = FALSE, b_writable = FALSE;
576 577 578
    NSString *uri, *name, *path;
    NSURL * url;
    NSArray *optionsArray;
579 580

    /* Get the item */
581 582 583 584 585
    uri = (NSString *)[itemToCreateDict objectForKey: @"ITEM_URL"];
    url = [NSURL URLWithString: uri];
    path = [url path];
    name = (NSString *)[itemToCreateDict objectForKey: @"ITEM_NAME"];
    optionsArray = (NSArray *)[itemToCreateDict objectForKey: @"ITEM_OPTIONS"];
586

587 588 589
    if ([[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&b_dir] && b_dir &&
        [[NSWorkspace sharedWorkspace] getFileSystemInfoForPath:path isRemovable: &b_rem
                                                     isWritable:&b_writable isUnmountable:NULL description:NULL type:NULL] && b_rem && !b_writable && [url isFileURL]) {
590

591
        NSString *diskType = [[VLCStringUtility sharedInstance] getVolumeTypeFromMountPath: path];
592
        msg_Dbg(p_intf, "detected optical media of type %s in the file input", [diskType UTF8String]);
593

594
        if ([diskType isEqualToString: kVLCMediaDVD])
595
            uri = [NSString stringWithFormat: @"dvdnav://%@", [[VLCStringUtility sharedInstance] getBSDNodeFromMountPath: path]];
596
        else if ([diskType isEqualToString: kVLCMediaVideoTSFolder])
597
            uri = [NSString stringWithFormat: @"dvdnav://%@", path];
598
        else if ([diskType isEqualToString: kVLCMediaAudioCD])
599
            uri = [NSString stringWithFormat: @"cdda://%@", [[VLCStringUtility sharedInstance] getBSDNodeFromMountPath: path]];
600
        else if ([diskType isEqualToString: kVLCMediaVCD])
601
            uri = [NSString stringWithFormat: @"vcd://%@#0:0", [[VLCStringUtility sharedInstance] getBSDNodeFromMountPath: path]];
602
        else if ([diskType isEqualToString: kVLCMediaSVCD])
603
            uri = [NSString stringWithFormat: @"vcd://%@@0:0", [[VLCStringUtility sharedInstance] getBSDNodeFromMountPath: path]];
604
        else if ([diskType isEqualToString: kVLCMediaBD] || [diskType isEqualToString: kVLCMediaBDMVFolder])
605
            uri = [NSString stringWithFormat: @"bluray://%@", path];
606
        else
607
            msg_Warn(getIntf(), "unknown disk type, treating %s as regular input", [path UTF8String]);
Benjamin Pracht's avatar
*all :  
Benjamin Pracht committed
608

609
        p_input = input_item_New([uri UTF8String], [[[NSFileManager defaultManager] displayNameAtPath:path] UTF8String]);
610
    }
611
    else
612
        p_input = input_item_New([uri fileSystemRepresentation], name ? [name UTF8String] : NULL);
613

614
    if (!p_input)
615
        return NULL;
616

617 618
    if (optionsArray) {
        NSUInteger count = [optionsArray count];
619
        for (NSUInteger i = 0; i < count; i++)
620
            input_item_AddOption(p_input, [optionsArray[i] UTF8String], VLC_INPUT_OPTION_TRUSTED);
621
    }
Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
622

623
    /* Recent documents menu */
624
    if (url != nil && (BOOL)config_GetInt(p_playlist, "macosx-recentitems") == YES)
625
        [[NSDocumentController sharedDocumentController] noteNewRecentDocumentURL:url];
626

627
    return p_input;
628 629
}

630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653
- (NSArray *)createItemsFromExternalPasteboard:(NSPasteboard *)pasteboard
{
    NSArray *o_array = [NSArray array];
    if (![[pasteboard types] containsObject: NSFilenamesPboardType])
        return o_array;

    NSArray *o_values = [[pasteboard propertyListForType: NSFilenamesPboardType] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
    NSUInteger count = [o_values count];

    for (NSUInteger i = 0; i < count; i++) {
        NSDictionary *o_dic;
        char *psz_uri = vlc_path2uri([[o_values objectAtIndex:i] UTF8String], NULL);
        if (!psz_uri)
            continue;

        o_dic = [NSDictionary dictionaryWithObject:toNSStr(psz_uri) forKey:@"ITEM_URL"];
        free(psz_uri);

        o_array = [o_array arrayByAddingObject: o_dic];
    }

    return o_array;
}

654
- (void)addPlaylistItems:(NSArray*)array
655 656
{

657
    int i_plItemId = -1;
658

659 660 661
    // add items directly to media library if this is the current root
    if ([[self model] currentRootType] == ROOT_TYPE_MEDIALIBRARY)
        i_plItemId = [[[self model] rootItem] plItemId];
Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
662

663
    BOOL b_autoplay = var_InheritBool(getIntf(), "macosx-autoplay");
Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
664

665
    [self addPlaylistItems:array withParentItemId:i_plItemId atPos:-1 startPlayback:b_autoplay];
666 667
}

668
- (void)addPlaylistItems:(NSArray*)array withParentItemId:(int)i_plItemId atPos:(int)i_position startPlayback:(BOOL)b_start
669
{
670
    playlist_t * p_playlist = pl_Get(getIntf());
671
    PL_LOCK;
672

673 674 675 676 677
    playlist_item_t *p_parent = NULL;
    if (i_plItemId >= 0)
        p_parent = playlist_ItemGetById(p_playlist, i_plItemId);
    else
        p_parent = p_playlist->p_playing;
678

679 680 681 682 683
    if (!p_parent) {
        PL_UNLOCK;
        return;
    }

684
    NSUInteger count = [array count];
685 686
    int i_current_offset = 0;
    for (NSUInteger i = 0; i < count; ++i) {
687

688
        NSDictionary *o_current_item = array[i];
689
        input_item_t *p_input = [self createItem: o_current_item];
690 691
        if (!p_input)
            continue;
Benjamin Pracht's avatar
*all :  
Benjamin Pracht committed
692

693 694 695 696 697
        int i_pos = (i_position == -1) ? PLAYLIST_END : i_position + i_current_offset++;
        playlist_item_t *p_item = playlist_NodeAddInput(p_playlist, p_input, p_parent,
                                                        PLAYLIST_INSERT, i_pos, pl_Locked);
        if (!p_item)
            continue;
698

699 700
        if (i == 0 && b_start) {
            playlist_Control(p_playlist, PLAYLIST_VIEWPLAY, pl_Locked, p_parent, p_item);
Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
701
        }
702
        input_item_Release(p_input);
703
    }
704
    PL_UNLOCK;
Jon Lech Johansen's avatar
Jon Lech Johansen committed
705
}
706

707 708
- (IBAction)recursiveExpandNode:(id)sender
{
709
    NSIndexSet * selectedRows = [_outlineView selectedRowIndexes];
710 711 712
    NSUInteger count = [selectedRows count];
    NSUInteger indexes[count];
    [selectedRows getIndexes:indexes maxCount:count inIndexRange:nil];
713

714
    id item;
715 716
    playlist_item_t *p_item;
    for (NSUInteger i = 0; i < count; i++) {
717
        item = [_outlineView itemAtRow: indexes[i]];
718 719 720

        /* We need to collapse the node first, since OSX refuses to recursively
         expand an already expanded node, even if children nodes are collapsed. */
721 722 723
        if ([_outlineView isExpandable:item]) {
            [_outlineView collapseItem: item collapseChildren: YES];
            [_outlineView expandItem: item expandChildren: YES];
724
        }
725

726
        selectedRows = [_outlineView selectedRowIndexes];
727 728
        [selectedRows getIndexes:indexes maxCount:count inIndexRange:nil];
    }
729 730
}

Benjamin Pracht's avatar
Benjamin Pracht committed
731 732
- (NSMenu *)menuForEvent:(NSEvent *)o_event
{
733 734 735
    if (!b_playlistmenu_nib_loaded)
        b_playlistmenu_nib_loaded = [NSBundle loadNibNamed:@"PlaylistMenu" owner:self];

Benjamin Pracht's avatar
Benjamin Pracht committed
736
    NSPoint pt;
737 738
    bool b_rows;
    bool b_item_sel;
Benjamin Pracht's avatar
Benjamin Pracht committed
739

740 741 742 743
    pt = [_outlineView convertPoint: [o_event locationInWindow] fromView: nil];
    int row = [_outlineView rowAtPoint:pt];
    if (row != -1 && ![[_outlineView selectedRowIndexes] containsIndex: row])
        [_outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO];
744

745 746
    b_item_sel = (row != -1 && [_outlineView selectedRow] != -1);
    b_rows = [_outlineView numberOfRows] != 0;
Benjamin Pracht's avatar
Benjamin Pracht committed
747

748
    playlist_t *p_playlist = pl_Get(getIntf());
749
    bool b_del_allowed = [[self model] editAllowed];
750

751
    // TODO move other items to menu validation protocol
752 753 754 755 756 757 758 759
    [_infoPlaylistMenuItem setEnabled: b_item_sel];
    [_preparsePlaylistMenuItem setEnabled: b_item_sel];
    [_recursiveExpandPlaylistMenuItem setEnabled: b_item_sel];
    [_sortNamePlaylistMenuItem setEnabled: b_item_sel];
    [_sortAuthorPlaylistMenuItem setEnabled: b_item_sel];
    [_downloadCoverArtPlaylistMenuItem setEnabled: b_item_sel];

    return _playlistMenu;
Benjamin Pracht's avatar
Benjamin Pracht committed
760
}
761

762
- (void)outlineView:(NSOutlineView *)outlineView didClickTableColumn:(NSTableColumn *)aTableColumn
763
{
764
    int type = 0;
765
    intf_thread_t *p_intf = getIntf();
766
    NSString * identifier = [aTableColumn identifier];
767

768
    playlist_t *p_playlist = pl_Get(p_intf);
769

770
    if (_sortTableColumn == aTableColumn)
771 772
        b_isSortDescending = !b_isSortDescending;
    else
773
        b_isSortDescending = false;
774

775
    if (b_isSortDescending)
776
        type = ORDER_REVERSE;
777
    else
778
        type = ORDER_NORMAL;
779

780
    [[self model] sortForColumn:identifier withMode:type];
781 782 783 784 785

    // TODO rework, why do we need a full call here?
//    [self playlistUpdated];

    /* Clear indications of any existing column sorting */
786
    NSUInteger count = [[_outlineView tableColumns] count];
787
    for (NSUInteger i = 0 ; i < count ; i++)
788
        [_outlineView setIndicatorImage:nil inTableColumn: [_outlineView tableColumns][i]];
789

790
    [_outlineView setHighlightedTableColumn:nil];
791 792
    _sortTableColumn = aTableColumn;
    [_outlineView setHighlightedTableColumn:aTableColumn];
793

794
    if (b_isSortDescending)
795
        [_outlineView setIndicatorImage:_descendingSortingImage inTableColumn:aTableColumn];
796
    else
797
        [_outlineView setIndicatorImage:_ascendingSortingImage inTableColumn:aTableColumn];
798 799
}

800 801

- (void)outlineView:(NSOutlineView *)outlineView
802 803 804
    willDisplayCell:(id)cell
     forTableColumn:(NSTableColumn *)tableColumn
               item:(id)item
805
{
806
    /* this method can be called when VLC is already dead, hence the extra checks */
807
    intf_thread_t * p_intf = getIntf();
808 809
    if (!p_intf)
        return;
810
    playlist_t *p_playlist = pl_Get(p_intf);
811

812
    NSFont *fontToUse;
813
    if (var_InheritBool(getIntf(), "macosx-large-text"))
814 815 816 817
        fontToUse = [NSFont systemFontOfSize:13.];
    else
        fontToUse = [NSFont systemFontOfSize:11.];

818 819 820 821 822 823 824 825 826 827 828 829 830 831 832
    BOOL b_is_playing = NO;
    PL_LOCK;
    playlist_item_t *p_current_item = playlist_CurrentPlayingItem(p_playlist);
    if (p_current_item) {
        b_is_playing = p_current_item->i_id == [item plItemId];
    }
    PL_UNLOCK;

    /*
     TODO: repaint all items bold:
     [self isItem: [o_playing_item pointerValue] inNode: [item pointerValue] checkItemExistence:YES locked:NO]
     || [o_playing_item isEqual: item]
     */

    if (b_is_playing)
833
        [cell setFont: [[NSFontManager sharedFontManager] convertFont:fontToUse toHaveTrait:NSBoldFontMask]];
834
    else
835
        [cell setFont: [[NSFontManager sharedFontManager] convertFont:fontToUse toNotHaveTrait:NSBoldFontMask]];
836 837
}

838
// TODO remove method
839 840
- (NSArray *)draggedItems
{
841
    return [[self model] draggedItems];
842
}
843

844 845 846
- (BOOL)isValidResumeItem:(input_item_t *)p_item
{
    char *psz_url = input_item_GetURI(p_item);
847
    NSString *urlString = toNSStr(psz_url);
848 849
    free(psz_url);

850
    if ([urlString isEqualToString:@""])
851 852
        return NO;

853
    NSURL *url = [NSURL URLWithString:urlString];
854

855
    if (![url isFileURL])
856 857 858
        return NO;

    BOOL isDir = false;
859
    if (![[NSFileManager defaultManager] fileExistsAtPath:[url path] isDirectory:&isDir])
860 861 862 863 864 865 866 867
        return NO;

    if (isDir)
        return NO;

    return YES;
}

868
- (void)continuePlaybackWhereYouLeftOff:(input_thread_t *)p_input_thread
869 870
{
    NSDictionary *recentlyPlayedFiles = [[NSUserDefaults standardUserDefaults] objectForKey:@"recentlyPlayedMedia"];
871 872
    if (!recentlyPlayedFiles)
        return;
873

874 875 876
    input_item_t *p_item = input_GetItem(p_input_thread);
    if (!p_item)
        return;
877

878 879 880
    /* allow the user to over-write the start/stop/run-time */
    if (var_GetFloat(p_input_thread, "run-time") > 0 ||
        var_GetFloat(p_input_thread, "start-time") > 0 ||
881
        var_GetFloat(p_input_thread, "stop-time") != 0) {
882 883
        return;
    }
884

885 886 887
    /* check for file existance before resuming */
    if (![self isValidResumeItem:p_item])
        return;
888

889
    char *psz_url = vlc_uri_decode(input_item_GetURI(p_item));
890 891 892 893
    if (!psz_url)
        return;
    NSString *url = toNSStr(psz_url);
    free(psz_url);
894

895 896 897
    NSNumber *lastPosition = [recentlyPlayedFiles objectForKey:url];
    if (!lastPosition || lastPosition.intValue <= 0)
        return;
898