playlist.m 56.7 KB
Newer Older
1
/*****************************************************************************
2
 * playlist.m: MacOS X interface module
3
 *****************************************************************************
4
* Copyright (C) 2002-2008 the VideoLAN team
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 11 12 13 14
 *
 * 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
15
 *
16 17 18 19 20 21 22
 * 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
23
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
24 25
 *****************************************************************************/

26
/* TODO
27
 * add 'icons' for different types of nodes? (http://www.cocoadev.com/index.pl?IconAndTextInTableCell)
28 29 30 31 32 33
 * reimplement enable/disable item
 * create a new 'tool' button (see the gear button in the Finder window) for 'actions'
   (adding service discovery, other views, new node/playlist, save node/playlist) stuff like that
 */


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

44
#import "intf.h"
45 46
#import "wizard.h"
#import "bookmarks.h"
47
#import "playlistinfo.h"
48 49 50 51
#import "playlist.h"
#import "controls.h"
#import "vlc_osd.h"
#import "misc.h"
Clément Stenac's avatar
Clément Stenac committed
52
#import <vlc_interface.h>
Felix Paul Kühne's avatar
Felix Paul Kühne committed
53
#import <vlc_services_discovery.h>
54

55
/*****************************************************************************
56
 * VLCPlaylistView implementation
57 58 59 60 61
 *****************************************************************************/
@implementation VLCPlaylistView

- (NSMenu *)menuForEvent:(NSEvent *)o_event
{
Jon Lech Johansen's avatar
Jon Lech Johansen committed
62
    return( [[self delegate] menuForEvent: o_event] );
63 64
}

65 66 67
- (void)keyDown:(NSEvent *)o_event
{
    unichar key = 0;
Benjamin Pracht's avatar
*all :  
Benjamin Pracht committed
68

69 70 71 72 73 74 75
    if( [[o_event characters] length] )
    {
        key = [[o_event characters] characterAtIndex: 0];
    }

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

83 84
        case NSEnterCharacter:
        case NSCarriageReturnCharacter:
85
            [(VLCPlaylist *)[[VLCMain sharedInstance] getPlaylist] playItem:self];
86 87
            break;

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

94 95 96
@end

/*****************************************************************************
97 98 99 100 101 102 103
 * VLCPlaylistCommon implementation
 *
 * This class the superclass of the VLCPlaylist and VLCPlaylistWizard.
 * It contains the common methods and elements of these 2 entities.
 *****************************************************************************/
@implementation VLCPlaylistCommon

104 105 106 107 108 109 110 111
- (id)init
{
    self = [super init];
    if ( self != nil )
    {
        o_outline_dict = [[NSMutableDictionary alloc] init];
    }
    return self;
112
}
113 114
- (void)awakeFromNib
{
Antoine Cellerier's avatar
Antoine Cellerier committed
115
    playlist_t * p_playlist = pl_Hold( VLCIntf );
116 117 118
    [o_outline_view setTarget: self];
    [o_outline_view setDelegate: self];
    [o_outline_view setDataSource: self];
119
    [o_outline_view setAllowsEmptySelection: NO];
120
    [o_outline_view expandItem: [o_outline_view itemAtRow:0]];
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151

    vlc_object_release( p_playlist );
    [self initStrings];
}

- (void)initStrings
{
    [[o_tc_name headerCell] setStringValue:_NS("Name")];
    [[o_tc_author headerCell] setStringValue:_NS("Author")];
    [[o_tc_duration headerCell] setStringValue:_NS("Duration")];
}

- (NSOutlineView *)outlineView
{
    return o_outline_view;
}

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

@end

@implementation VLCPlaylistCommon (NSOutlineViewDataSource)

/* return the number of children for Obj-C pointer item */ /* DONE */
- (int)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
{
    int i_return = 0;
152
    playlist_item_t *p_item = NULL;
Antoine Cellerier's avatar
Antoine Cellerier committed
153
    playlist_t * p_playlist = pl_Hold( VLCIntf );
154
    assert( outlineView == o_outline_view );
155

156
    if( !item )
157
        p_item = p_playlist->p_root_category;
158
    else
159
        p_item = (playlist_item_t *)[item pointerValue];
160

161
    if( p_item )
162
        i_return = p_item->i_children;
163 164

    pl_Release( VLCIntf );
165

166
    return i_return > 0 ? i_return : 0;
167 168 169 170 171
}

/* return the child at index for the Obj-C pointer item */ /* DONE */
- (id)outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item
{
172 173
    playlist_item_t *p_return = NULL, *p_item = NULL;
    NSValue *o_value;
Antoine Cellerier's avatar
Antoine Cellerier committed
174
    playlist_t * p_playlist = pl_Hold( VLCIntf );
175

176
    PL_LOCK;
177 178 179
    if( item == nil )
    {
        /* root object */
180
        p_item = p_playlist->p_root_category;
181 182 183
    }
    else
    {
184
        p_item = (playlist_item_t *)[item pointerValue];
185
    }
186 187
    if( p_item && index < p_item->i_children && index >= 0 )
        p_return = p_item->pp_children[index];
188 189
    PL_UNLOCK;

190 191
    vlc_object_release( p_playlist );

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

194 195
    if( o_value == nil )
    {
196 197 198
        /* Why is there a warning if that happens all the time and seems
         * to be normal? Add an assert and fix it. 
         * msg_Warn( VLCIntf, "playlist item misses pointer value, adding one" ); */
199 200
        o_value = [[NSValue valueWithPointer: p_return] retain];
    }
201 202 203 204 205 206 207
    return o_value;
}

/* is the item expandable */
- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
{
    int i_return = 0;
Antoine Cellerier's avatar
Antoine Cellerier committed
208
    playlist_t *p_playlist = pl_Hold( VLCIntf );
209 210 211 212

    if( item == nil )
    {
        /* root object */
213
        if( p_playlist->p_root_category )
214
        {
215
            i_return = p_playlist->p_root_category->i_children;
216 217 218 219 220 221 222 223
        }
    }
    else
    {
        playlist_item_t *p_item = (playlist_item_t *)[item pointerValue];
        if( p_item )
            i_return = p_item->i_children;
    }
224
    pl_Release( VLCIntf );
225

226
    return (i_return >= 0);
227 228 229 230 231 232 233
}

/* retrieve the string values for the cells */
- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)o_tc byItem:(id)item
{
    id o_value = nil;
    playlist_item_t *p_item;
234 235 236 237 238 239 240 241 242 243 244 245 246 247 248

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

    if( item == nil || ![item isKindOfClass: [NSValue class]] )
    {
        /* Attempt to fix the error by asking for a data redisplay
         * This might cause infinite loop, so add a small check */
        if( !attempted_reload )
        {
            attempted_reload = YES;
            [outlineView reloadData];
        }
        return @"error" ;
    }
249
 
250
    p_item = (playlist_item_t *)[item pointerValue];
251
    if( !p_item || !p_item->p_input )
252
    {
253 254 255 256 257 258 259 260
        /* Attempt to fix the error by asking for a data redisplay
         * This might cause infinite loop, so add a small check */
        if( !attempted_reload )
        {
            attempted_reload = YES;
            [outlineView reloadData];
        }
        return @"error";
261
    }
262
 
263 264
    attempted_reload = NO;

265
    if( [[o_tc identifier] isEqualToString:@"name"] )
266
    {
Felix Paul Kühne's avatar
Felix Paul Kühne committed
267
        /* sanity check to prevent the NSString class from crashing */
268
        char *psz_title =  input_item_GetTitle( p_item->p_input );
269
        if( !EMPTY_STR( psz_title ) )
270
        {
271
            o_value = [NSString stringWithUTF8String: psz_title];
272
        }
273
        else
Felix Paul Kühne's avatar
Felix Paul Kühne committed
274
        {
275
            char *psz_name = input_item_GetName( p_item->p_input );
276
            if( psz_name )
277
                o_value = [NSString stringWithUTF8String: psz_name];
278
            free( psz_name );
Felix Paul Kühne's avatar
Felix Paul Kühne committed
279
        }
280
        free( psz_title );
281
    }
282
    else if( [[o_tc identifier] isEqualToString:@"artist"] )
283
    {
284
        char *psz_artist = input_item_GetArtist( p_item->p_input );
285
        if( psz_artist )
286
            o_value = [NSString stringWithUTF8String: psz_artist];
287 288 289 290 291 292 293 294 295 296
        free( psz_artist );
    }
    else if( [[o_tc identifier] isEqualToString:@"duration"] )
    {
        char psz_duration[MSTRTIME_MAX_SIZE];
        mtime_t dur = input_item_GetDuration( p_item->p_input );
        if( dur != -1 )
        {
            secstotimestr( psz_duration, dur/1000000 );
            o_value = [NSString stringWithUTF8String: psz_duration];
297
        }
298 299 300 301 302
        else
            o_value = @"--:--";
    }
    else if( [[o_tc identifier] isEqualToString:@"status"] )
    {
303
        if( input_item_HasErrorWhenReading( p_item->p_input ) )
304
        {
305
            o_value = [NSImage imageWithWarningIcon];
306 307
        }
    }
308
    return o_value;
309 310 311 312 313 314 315 316 317 318 319
}

@end

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

- (IBAction)reloadOutlineView
{
320 321 322 323 324 325
    /* Only reload the outlineview if the wizard window is open since this can
       be quite long on big playlists */
    if( [[o_outline_view window] isVisible] )
    {
        [o_outline_view reloadData];
    }
326 327 328 329
}

@end

330 331 332 333 334 335 336 337 338 339 340 341 342
/*****************************************************************************
 * extension to NSOutlineView's interface to fix compilation warnings
 * and let us access these 2 functions properly
 * this uses a private Apple-API, but works fine on all current OSX releases
 * keep checking for compatiblity with future releases though
 *****************************************************************************/

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


343 344
/*****************************************************************************
 * VLCPlaylist implementation
345 346 347
 *****************************************************************************/
@implementation VLCPlaylist

348 349 350
- (id)init
{
    self = [super init];
Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
351
    if ( self != nil )
352
    {
Benjamin Pracht's avatar
Benjamin Pracht committed
353 354
        o_nodes_array = [[NSMutableArray alloc] init];
        o_items_array = [[NSMutableArray alloc] init];
355 356 357 358
    }
    return self;
}

359 360 361 362 363 364 365
- (void)dealloc
{
    [o_nodes_array release];
    [o_items_array release];
    [super dealloc];
}

366 367
- (void)awakeFromNib
{
Antoine Cellerier's avatar
Antoine Cellerier committed
368
    playlist_t * p_playlist = pl_Hold( VLCIntf );
369

370
    int i;
371

372
    [super awakeFromNib];
373

374
    [o_outline_view setDoubleAction: @selector(playItem:)];
375

376
    [o_outline_view registerForDraggedTypes:
Benjamin Pracht's avatar
Benjamin Pracht committed
377 378
        [NSArray arrayWithObjects: NSFilenamesPboardType,
        @"VLCPlaylistItemPboardType", nil]];
379
    [o_outline_view setIntercellSpacing: NSMakeSize (0.0, 1.0)];
380

381
    /* This uses private Apple API which works fine until 10.5.
382
     * We need to keep checking in the future!
383 384 385
     * These methods are being added artificially to NSOutlineView's interface above */
    o_ascendingSortingImage = [[NSOutlineView class] _defaultTableHeaderSortImage];
    o_descendingSortingImage = [[NSOutlineView class] _defaultTableHeaderReverseSortImage];
Benjamin Pracht's avatar
Benjamin Pracht committed
386

387
    o_tc_sortColumn = nil;
388

389
    char ** ppsz_name;
390
    char ** ppsz_services = vlc_sd_GetNames( &ppsz_name );
391 392 393 394 395
    if( !ppsz_services )
    {
        vlc_object_release( p_playlist );
        return;
    }
396 397
    
    for( i = 0; ppsz_services[i]; i++ )
398
    {
399
        bool  b_enabled;
400
        NSMenuItem  *o_lmi;
401

402 403
        char * name = ppsz_name[i] ? ppsz_name[i] : ppsz_services[i];
        /* Check whether to enable these menuitems */
404
        b_enabled = playlist_IsServicesDiscoveryLoaded( p_playlist, ppsz_services[i] );
405 406 407 408 409 410 411

        /* Create the menu entries used in the playlist menu */
        o_lmi = [[o_mi_services submenu] addItemWithTitle:
                 [NSString stringWithUTF8String: name]
                                         action: @selector(servicesChange:)
                                         keyEquivalent: @""];
        [o_lmi setTarget: self];
412
        [o_lmi setRepresentedObject: [NSString stringWithUTF8String: ppsz_services[i]]];
413 414 415 416 417 418 419 420
        if( b_enabled ) [o_lmi setState: NSOnState];

        /* Create the menu entries for the main menu */
        o_lmi = [[o_mm_mi_services submenu] addItemWithTitle:
                 [NSString stringWithUTF8String: name]
                                         action: @selector(servicesChange:)
                                         keyEquivalent: @""];
        [o_lmi setTarget: self];
421
        [o_lmi setRepresentedObject: [NSString stringWithUTF8String: ppsz_services[i]]];
422 423
        if( b_enabled ) [o_lmi setState: NSOnState];

424 425
        free( ppsz_services[i] );
        free( ppsz_name[i] );
426
    }
427 428 429
    free( ppsz_services );
    free( ppsz_name );

430
    vlc_object_release( p_playlist );
431
}
432

433 434 435 436 437
- (void)searchfieldChanged:(NSNotification *)o_notification
{
    [o_search_field setStringValue:[[o_notification object] stringValue]];
}

438 439
- (void)initStrings
{
440 441
    [super initStrings];

442
    [o_mi_save_playlist setTitle: _NS("Save Playlist...")];
Jon Lech Johansen's avatar
Jon Lech Johansen committed
443 444
    [o_mi_play setTitle: _NS("Play")];
    [o_mi_delete setTitle: _NS("Delete")];
445
    [o_mi_recursive_expand setTitle: _NS("Expand Node")];
Jon Lech Johansen's avatar
Jon Lech Johansen committed
446
    [o_mi_selectall setTitle: _NS("Select All")];
447 448
    [o_mi_info setTitle: _NS("Media Information...")];
    [o_mi_dl_cover_art setTitle: _NS("Download Cover Art")];
449
    [o_mi_preparse setTitle: _NS("Fetch Meta Data")];
450 451 452
    [o_mi_revealInFinder setTitle: _NS("Reveal in Finder")];
    [o_mm_mi_revealInFinder setTitle: _NS("Reveal in Finder")];
    [[o_mm_mi_revealInFinder menu] setAutoenablesItems: NO];
453 454
    [o_mi_sort_name setTitle: _NS("Sort Node by Name")];
    [o_mi_sort_author setTitle: _NS("Sort Node by Author")];
455
    [o_mi_services setTitle: _NS("Services discovery")];
456
    [o_mm_mi_services setTitle: _NS("Services discovery")];
457
    [o_status_field setStringValue: _NS("No items in the playlist")];
Benjamin Pracht's avatar
Benjamin Pracht committed
458

459
    [o_search_field setToolTip: _NS("Search in Playlist")];
460
    [o_mi_addNode setTitle: _NS("Add Folder to Playlist")];
461 462 463 464

    [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)")];
465 466
}

467 468
- (void)playlistUpdated
{
469
    /* Clear indications of any existing column sorting */
470
    for( unsigned int i = 0 ; i < [[o_outline_view tableColumns] count] ; i++ )
471 472 473 474 475 476 477
    {
        [o_outline_view setIndicatorImage:nil inTableColumn:
                            [[o_outline_view tableColumns] objectAtIndex:i]];
    }

    [o_outline_view setHighlightedTableColumn:nil];
    o_tc_sortColumn = nil;
Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
478 479
    // TODO Find a way to keep the dict size to a minimum
    //[o_outline_dict removeAllObjects];
480
    [o_outline_view reloadData];
481
    [[[[VLCMain sharedInstance] getWizard] getPlaylistWizard] reloadOutlineView];
482
    [[[[VLCMain sharedInstance] getBookmarks] getDataTable] reloadData];
483

Antoine Cellerier's avatar
Antoine Cellerier committed
484
    playlist_t *p_playlist = pl_Hold( VLCIntf );
485

486
    PL_LOCK;
487
    if( playlist_CurrentSize( p_playlist ) >= 2 )
488 489
    {
        [o_status_field setStringValue: [NSString stringWithFormat:
Felix Paul Kühne's avatar
Felix Paul Kühne committed
490
                    _NS("%i items"),
491
                playlist_CurrentSize( p_playlist )]];
492 493 494
    }
    else
    {
Clément Stenac's avatar
Clément Stenac committed
495
        if( playlist_IsEmpty( p_playlist ) )
496 497
            [o_status_field setStringValue: _NS("No items in the playlist")];
        else
Felix Paul Kühne's avatar
Felix Paul Kühne committed
498
            [o_status_field setStringValue: _NS("1 item")];
499
    }
500
    PL_UNLOCK;
501
    vlc_object_release( p_playlist );
502 503

    [self outlineViewSelectionDidChange: nil];
504
}
505

506 507
- (void)playModeUpdated
{
Antoine Cellerier's avatar
Antoine Cellerier committed
508
    playlist_t *p_playlist = pl_Hold( VLCIntf );
509

Pierre d'Herbemont's avatar
Pierre d'Herbemont committed
510 511 512
    bool loop = var_GetBool( p_playlist, "loop" );
    bool repeat = var_GetBool( p_playlist, "repeat" );
    if( repeat )
513
        [[[VLCMain sharedInstance] getControls] repeatOne];
Pierre d'Herbemont's avatar
Pierre d'Herbemont committed
514
    else if( loop )
515
        [[[VLCMain sharedInstance] getControls] repeatAll];
516
    else
517
        [[[VLCMain sharedInstance] getControls] repeatOff];
518

519
    [[[VLCMain sharedInstance] getControls] shuffle];
520 521 522 523

    vlc_object_release( p_playlist );
}

524 525 526 527 528 529 530
- (void)outlineViewSelectionDidChange:(NSNotification *)notification
{
    // FIXME: unsafe
    playlist_item_t * p_item = [[o_outline_view itemAtRow:[o_outline_view selectedRow]] pointerValue];

    if( p_item )
    {
531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551
        /* update the state of our Reveal-in-Finder menu items */
        NSMutableString *o_mrl;
        char *psz_uri = input_item_GetURI( p_item->p_input );
        if( psz_uri )
        {
            o_mrl = [NSMutableString stringWithUTF8String: psz_uri];
        
            /* perform some checks whether it is a file and if it is local at all... */
            NSRange prefix_range = [o_mrl rangeOfString: @"file:"];
            if( prefix_range.location != NSNotFound )
                [o_mrl deleteCharactersInRange: prefix_range];
            
            if( [o_mrl characterAtIndex:0] == '/' )
            {
                [o_mi_revealInFinder setEnabled: YES];
                [o_mm_mi_revealInFinder setEnabled: YES];
                return;
            }
        }
        [o_mi_revealInFinder setEnabled: NO];
        [o_mm_mi_revealInFinder setEnabled: NO];
552 553 554 555 556 557

        if( [[VLCMain sharedInstance] isPlaylistCollapsed] == NO )
        {
            /* update our info-panel to reflect the new item, if we aren't collapsed */
            [[[VLCMain sharedInstance] getInfo] updatePanelWithItem:p_item->p_input];
        }
558 559 560 561 562 563 564 565
    }
}

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

566 567
- (void)updateRowSelection
{
568
    int i_row;
569
    unsigned int j;
570

571
    // FIXME: unsafe
Antoine Cellerier's avatar
Antoine Cellerier committed
572
    playlist_t *p_playlist = pl_Hold( VLCIntf );
Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
573 574
    playlist_item_t *p_item, *p_temp_item;
    NSMutableArray *o_array = [NSMutableArray array];
575

576
    p_item = playlist_CurrentPlayingItem( p_playlist );
577 578
    if( p_item == NULL )
    {
579
        pl_Release( VLCIntf );
580 581
        return;
    }
582

583
    p_temp_item = p_item;
584
    while( p_temp_item->p_parent )
585 586
    {
        [o_array insertObject: [NSValue valueWithPointer: p_temp_item] atIndex: 0];
587
        p_temp_item = p_temp_item->p_parent;
588 589
    }

590
    for( j = 0; j < [o_array count] - 1; j++ )
591
    {
592 593
        id o_item;
        if( ( o_item = [o_outline_dict objectForKey:
594
                            [NSString stringWithFormat: @"%p",
595
                            [[o_array objectAtIndex:j] pointerValue]]] ) != nil )
596
        {
597
            [o_outline_view expandItem: o_item];
598
        }
599 600 601

    }

602
    pl_Release( VLCIntf );
603 604
}

605 606
/* 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
607
   deleted. We don't do it when not required since this verification takes
608
   quite a long time on big playlists (yes, pretty hacky). */
609 610 611 612 613

- (BOOL)isItem: (playlist_item_t *)p_item
                    inNode: (playlist_item_t *)p_node
                    checkItemExistence:(BOOL)b_check
                    locked:(BOOL)b_locked
614

615
{
Antoine Cellerier's avatar
Antoine Cellerier committed
616
    playlist_t * p_playlist = pl_Hold( VLCIntf );
617 618
    playlist_item_t *p_temp_item = p_item;

619 620 621 622 623 624 625 626 627 628 629
    if( p_node == p_item )
    {
        vlc_object_release(p_playlist);
        return YES;
    }

    if( p_node->i_children < 1)
    {
        vlc_object_release(p_playlist);
        return NO;
    }
Benjamin Pracht's avatar
Benjamin Pracht committed
630

631 632
    if ( p_temp_item )
    {
633
        int i;
634
        if(!b_locked) PL_LOCK;
635

636 637
        if( b_check )
        {
638 639 640
        /* 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. */
641
            for( i = 0; i < p_playlist->all_items.i_size; i++ )
642
            {
643 644
                if( ARRAY_VAL( p_playlist->all_items, i) == p_item ) break;
                else if ( i == p_playlist->all_items.i_size - 1 )
645
                {
646
                    if(!b_locked) PL_UNLOCK;
647 648 649
                    vlc_object_release( p_playlist );
                    return NO;
                }
650 651
            }
        }
652

653
        while( p_temp_item )
654
        {
655
            p_temp_item = p_temp_item->p_parent;
656 657
            if( p_temp_item == p_node )
            {
658
                if(!b_locked) PL_UNLOCK;
659 660
                vlc_object_release( p_playlist );
                return YES;
661
            }
662
        }
663
        if(!b_locked) PL_UNLOCK;
664 665 666 667 668 669
    }

    vlc_object_release( p_playlist );
    return NO;
}

670 671 672 673 674 675 676
- (BOOL)isItem: (playlist_item_t *)p_item
                    inNode: (playlist_item_t *)p_node
                    checkItemExistence:(BOOL)b_check
{
    [self isItem:p_item inNode:p_node checkItemExistence:b_check locked:NO];
}

677
/* This method is usefull for instance to remove the selected children of an
678
   already selected node */
679 680 681 682 683 684 685 686 687 688 689 690
- (void)removeItemsFrom:(id)o_items ifChildrenOf:(id)o_nodes
{
    unsigned int i, j;
    for( i = 0 ; i < [o_items count] ; i++ )
    {
        for ( j = 0 ; j < [o_nodes count] ; j++ )
        {
            if( o_items == o_nodes)
            {
                if( j == i ) continue;
            }
            if( [self isItem: [[o_items objectAtIndex:i] pointerValue]
691
                    inNode: [[o_nodes objectAtIndex:j] pointerValue]
692
                    checkItemExistence: NO locked:NO] )
693
            {
694
                [o_items removeObjectAtIndex:i];
695 696 697 698 699 700 701 702 703
                /* We need to execute the next iteration with the same index
                   since the current item has been deleted */
                i--;
                break;
            }
        }
    }
}

704 705
- (IBAction)savePlaylist:(id)sender
{
Antoine Cellerier's avatar
Antoine Cellerier committed
706
    playlist_t * p_playlist = pl_Hold( VLCIntf );
707 708

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

    //[o_save_panel setAllowedFileTypes: [NSArray arrayWithObjects: @"m3u", @"xpf", nil] ];
712 713
    [o_save_panel setTitle: _NS("Save Playlist")];
    [o_save_panel setPrompt: _NS("Save")];
714
    [o_save_panel setAccessoryView: o_save_accessory_view];
715 716 717 718

    if( [o_save_panel runModalForDirectory: nil
            file: o_name] == NSOKButton )
    {
719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736
        NSString *o_filename = [o_save_panel filename];

        if( [o_save_accessory_popup indexOfSelectedItem] == 1 )
        {
            NSString * o_real_filename;
            NSRange range;
            range.location = [o_filename length] - [@".xspf" length];
            range.length = [@".xspf" length];

            if( [o_filename compare:@".xspf" options: NSCaseInsensitiveSearch
                                             range: range] != NSOrderedSame )
            {
                o_real_filename = [NSString stringWithFormat: @"%@.xspf", o_filename];
            }
            else
            {
                o_real_filename = o_filename;
            }
737 738
            playlist_Export( p_playlist,
                [o_real_filename fileSystemRepresentation],
Felix Paul Kühne's avatar
Felix Paul Kühne committed
739
                p_playlist->p_local_category, "export-xspf" );
740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756
        }
        else
        {
            NSString * o_real_filename;
            NSRange range;
            range.location = [o_filename length] - [@".m3u" length];
            range.length = [@".m3u" length];

            if( [o_filename compare:@".m3u" options: NSCaseInsensitiveSearch
                                             range: range] != NSOrderedSame )
            {
                o_real_filename = [NSString stringWithFormat: @"%@.m3u", o_filename];
            }
            else
            {
                o_real_filename = o_filename;
            }
757
            playlist_Export( p_playlist,
Felix Paul Kühne's avatar
Felix Paul Kühne committed
758 759
                [o_real_filename fileSystemRepresentation],
                p_playlist->p_local_category, "export-m3u" );
760
        }
761
    }
762
    vlc_object_release( p_playlist );
763 764
}

Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
765
/* When called retrieves the selected outlineview row and plays that node or item */
766 767 768
- (IBAction)playItem:(id)sender
{
    intf_thread_t * p_intf = VLCIntf;
Antoine Cellerier's avatar
Antoine Cellerier committed
769
    playlist_t * p_playlist = pl_Hold( p_intf );
770

771 772
    playlist_item_t *p_item;
    playlist_item_t *p_node = NULL;
Benjamin Pracht's avatar
 
Benjamin Pracht committed
773

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

776 777 778
    if( p_item )
    {
        if( p_item->i_children == -1 )
Benjamin Pracht's avatar
 
Benjamin Pracht committed
779
        {
780
            p_node = p_item->p_parent;
781

782 783 784 785 786 787 788
        }
        else
        {
            p_node = p_item;
            if( p_node->i_children > 0 && p_node->pp_children[0]->i_children == -1 )
            {
                p_item = p_node->pp_children[0];
Benjamin Pracht's avatar
 
Benjamin Pracht committed
789 790 791
            }
            else
            {
792
                p_item = NULL;
Benjamin Pracht's avatar
 
Benjamin Pracht committed
793 794
            }
        }
795
        playlist_Control( p_playlist, PLAYLIST_VIEWPLAY, pl_Unlocked, p_node, p_item );
796
    }
797
    vlc_object_release( p_playlist );
798
}
799

800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820
- (IBAction)revealItemInFinder:(id)sender
{
    playlist_item_t * p_item = [[o_outline_view itemAtRow:[o_outline_view selectedRow]] pointerValue];
    NSMutableString * o_mrl = nil;

    if(! p_item || !p_item->p_input )
        return;
    
    char *psz_uri = input_item_GetURI( p_item->p_input );
    if( psz_uri )
        o_mrl = [NSMutableString stringWithUTF8String: psz_uri];

    /* perform some checks whether it is a file and if it is local at all... */
    NSRange prefix_range = [o_mrl rangeOfString: @"file:"];
    if( prefix_range.location != NSNotFound )
        [o_mrl deleteCharactersInRange: prefix_range];
    
    if( [o_mrl characterAtIndex:0] == '/' )
        [[NSWorkspace sharedWorkspace] selectFile: o_mrl inFileViewerRootedAtPath: o_mrl];
}    

821 822 823 824 825 826
/* When called retrieves the selected outlineview row and plays that node or item */
- (IBAction)preparseItem:(id)sender
{
    int i_count;
    NSMutableArray *o_to_preparse;
    intf_thread_t * p_intf = VLCIntf;
Antoine Cellerier's avatar
Antoine Cellerier committed
827
    playlist_t * p_playlist = pl_Hold( p_intf );
828
 
829 830 831
    o_to_preparse = [NSMutableArray arrayWithArray:[[o_outline_view selectedRowEnumerator] allObjects]];
    i_count = [o_to_preparse count];

832 833 834 835 836
    int i, i_row;
    NSNumber *o_number;
    playlist_item_t *p_item = NULL;

    for( i = 0; i < i_count; i++ )
837
    {
838 839 840 841 842
        o_number = [o_to_preparse lastObject];
        i_row = [o_number intValue];
        p_item = [[o_outline_view itemAtRow:i_row] pointerValue];
        [o_to_preparse removeObject: o_number];
        [o_outline_view deselectRow: i_row];
843

844
        if( p_item )
845
        {
846
            if( p_item->i_children == -1 )
847
            {
848
                playlist_PreparseEnqueue( p_playlist, p_item->p_input, pl_Unlocked );
849 850 851
            }
            else
            {
852
                msg_Dbg( p_intf, "preparsing nodes not implemented" );
853 854 855
            }
        }
    }
856
    vlc_object_release( p_playlist );
857 858 859
    [self playlistUpdated];
}

860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883
- (IBAction)downloadCoverArt:(id)sender
{
    int i_count;
    NSMutableArray *o_to_preparse;
    intf_thread_t * p_intf = VLCIntf;
    playlist_t * p_playlist = pl_Hold( p_intf );

    o_to_preparse = [NSMutableArray arrayWithArray:[[o_outline_view selectedRowEnumerator] allObjects]];
    i_count = [o_to_preparse count];

    int i, i_row;
    NSNumber *o_number;
    playlist_item_t *p_item = NULL;

    for( i = 0; i < i_count; i++ )
    {
        o_number = [o_to_preparse lastObject];
        i_row = [o_number intValue];
        p_item = [[o_outline_view itemAtRow:i_row] pointerValue];
        [o_to_preparse removeObject: o_number];
        [o_outline_view deselectRow: i_row];

        if( p_item && p_item->i_children == -1 )
        {
884
            playlist_AskForArtEnqueue( p_playlist, p_item->p_input, pl_Unlocked );
885 886 887 888 889 890
        }
    }
    vlc_object_release( p_playlist );
    [self playlistUpdated];
}

891 892 893 894
- (IBAction)servicesChange:(id)sender
{
    NSMenuItem *o_mi = (NSMenuItem *)sender;
    NSString *o_string = [o_mi representedObject];
Antoine Cellerier's avatar
Antoine Cellerier committed
895
    playlist_t * p_playlist = pl_Hold( VLCIntf );
896 897
    if( !playlist_IsServicesDiscoveryLoaded( p_playlist, [o_string UTF8String] ) )
        playlist_ServicesDiscoveryAdd( p_playlist, [o_string UTF8String] );
898
    else
899
        playlist_ServicesDiscoveryRemove( p_playlist, [o_string UTF8String] );
900 901

    [o_mi setState: playlist_IsServicesDiscoveryLoaded( p_playlist,
902
                                          [o_string UTF8String] ) ? YES : NO];
903

904
    vlc_object_release( p_playlist );
905 906 907 908
    [self playlistUpdated];
    return;
}

Benjamin Pracht's avatar
Benjamin Pracht committed
909 910 911 912 913
- (IBAction)selectAll:(id)sender
{
    [o_outline_view selectAll: nil];
}

Benjamin Pracht's avatar
Benjamin Pracht committed
914 915
- (IBAction)deleteItem:(id)sender
{
916
    int i_count, i_row;
Benjamin Pracht's avatar
Benjamin Pracht committed
917 918 919 920 921 922 923
    NSMutableArray *o_to_delete;
    NSNumber *o_number;

    playlist_t * p_playlist;
    intf_thread_t * p_intf = VLCIntf;

    o_to_delete = [NSMutableArray arrayWithArray:[[o_outline_view selectedRowEnumerator] allObjects]];
Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
924
    i_count = [o_to_delete count];
Benjamin Pracht's avatar
Benjamin Pracht committed
925

Antoine Cellerier's avatar
Antoine Cellerier committed
926
    p_playlist = pl_Hold( p_intf );
927 928 929

    PL_LOCK;
    for( int i = 0; i < i_count; i++ )
Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
930
    {
Benjamin Pracht's avatar
Benjamin Pracht committed
931 932
        o_number = [o_to_delete lastObject];
        i_row = [o_number intValue];
933 934
        id o_item = [o_outline_view itemAtRow: i_row];
        playlist_item_t *p_item = [o_item pointerValue];
935 936 937 938
#ifndef NDEBUG
        msg_Dbg( p_intf, "deleting item %i (of %i) with id \"%i\", pointerValue