VLCMainWindow.m 50.1 KB
Newer Older
1
/*****************************************************************************
2
 * VLCMainWindow.m: MacOS X interface module
3
 *****************************************************************************
4
 * Copyright (C) 2002-2013 VLC authors and VideoLAN
5 6 7
 * $Id$
 *
 * Authors: Felix Paul Kühne <fkuehne -at- videolan -dot- org>
8 9 10
 *          Jon Lech Johansen <jon-vl@nanocrew.net>
 *          Christophe Massiot <massiot@via.ecp.fr>
 *          Derk-Jan Hartman <hartman at videolan.org>
11
 *          David Fuhrmann <david dot fuhrmann at googlemail dot com>
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
 *
 * 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.
 *
 * 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
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 *****************************************************************************/

28 29
#import "VLCMainWindow.h"

30
#import "VLCMain.h"
31
#import "CompatibilityFixes.h"
32
#import "VLCCoreInteraction.h"
33
#import "VLCAudioEffectsWindowController.h"
34
#import "VLCMainMenu.h"
35
#import "VLCOpenWindowController.h"
36
#import "VLCPlaylist.h"
37
#import "SideBarItem.h"
38
#import <math.h>
39
#import <vlc_playlist.h>
40 41
#import <vlc_url.h>
#import <vlc_strings.h>
42
#import <vlc_services_discovery.h>
43
#import "VLCPLModel.h"
44

45 46 47
#import "PXSourceList.h"
#import "PXSourceListDataSource.h"

48
#import "VLCMainWindowControlsBar.h"
49
#import "VLCVoutView.h"
50
#import "VLCVoutWindowController.h"
51 52


53
@interface VLCMainWindow() <PXSourceListDataSource, PXSourceListDelegate, NSOutlineViewDataSource, NSOutlineViewDelegate, NSWindowDelegate, NSAnimationDelegate, NSSplitViewDelegate>
54
{
55 56 57 58 59
    BOOL videoPlaybackEnabled;
    BOOL dropzoneActive;
    BOOL splitViewRemoved;
    BOOL minimizedView;

60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
    BOOL b_video_playback_enabled;
    BOOL b_dropzone_active;
    BOOL b_splitview_removed;
    BOOL b_minimized_view;

    CGFloat f_lastSplitViewHeight;
    CGFloat f_lastLeftSplitViewWidth;

    NSMutableArray *o_sidebaritems;

    /* this is only true, when we have NO video playing inside the main window */

    BOOL b_podcastView_displayed;

    VLCColorView * o_color_backdrop;

    NSRect frameBeforePlayback;
}
78 79 80
- (void)resizePlaylistAfterCollapse;
- (void)makeSplitViewVisible;
- (void)makeSplitViewHidden;
81 82
- (void)showPodcastControls;
- (void)hidePodcastControls;
83 84
@end

85
static const float f_min_window_height = 307.;
86

87 88
@implementation VLCMainWindow

89 90 91
#pragma mark -
#pragma mark Initialization

92 93 94 95 96
- (BOOL)isEvent:(NSEvent *)o_event forKey:(const char *)keyString
{
    char *key;
    NSString *o_key;

97
    key = config_GetPsz(getIntf(), keyString);
98
    o_key = [NSString stringWithFormat:@"%s", key];
99
    FREENULL(key);
100

101
    unsigned int i_keyModifiers = [[VLCStringUtility sharedInstance] VLCModifiersToCocoa:o_key];
102

103 104
    NSString * characters = [o_event charactersIgnoringModifiers];
    if ([characters length] > 0) {
105
        return [[characters lowercaseString] isEqualToString: [[VLCStringUtility sharedInstance] VLCKeyToString: o_key]] &&
106 107 108 109 110 111
                (i_keyModifiers & NSShiftKeyMask)     == ([o_event modifierFlags] & NSShiftKeyMask) &&
                (i_keyModifiers & NSControlKeyMask)   == ([o_event modifierFlags] & NSControlKeyMask) &&
                (i_keyModifiers & NSAlternateKeyMask) == ([o_event modifierFlags] & NSAlternateKeyMask) &&
                (i_keyModifiers & NSCommandKeyMask)   == ([o_event modifierFlags] & NSCommandKeyMask);
    }
    return NO;
112 113
}

114 115
- (BOOL)performKeyEquivalent:(NSEvent *)o_event
{
116
    BOOL b_force = NO;
117
    // these are key events which should be handled by vlc core, but are attached to a main menu item
118
    if (![self isEvent: o_event forKey: "key-vol-up"] &&
119
        ![self isEvent: o_event forKey: "key-vol-down"] &&
120 121 122 123 124
        ![self isEvent: o_event forKey: "key-vol-mute"] &&
        ![self isEvent: o_event forKey: "key-prev"] &&
        ![self isEvent: o_event forKey: "key-next"] &&
        ![self isEvent: o_event forKey: "key-jump+short"] &&
        ![self isEvent: o_event forKey: "key-jump-short"]) {
125 126
        /* We indeed want to prioritize some Cocoa key equivalent against libvlc,
         so we perform the menu equivalent now. */
127
        if ([[NSApp mainMenu] performKeyEquivalent:o_event])
128 129
            return TRUE;
    }
130 131
    else
        b_force = YES;
132

133 134 135
    VLCCoreInteraction *coreInteraction = [VLCCoreInteraction sharedInstance];
    return [coreInteraction hasDefinedShortcutKey:o_event force:b_force] ||
           [coreInteraction keyEvent:o_event];
136 137 138 139
}

- (void)dealloc
{
140
    [[NSNotificationCenter defaultCenter] removeObserver: self];
141 142 143 144
}

- (void)awakeFromNib
{
145 146
    [super awakeFromNib];

147 148 149 150 151 152 153 154
    /*
     * General setup
     */

    NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter];
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

    BOOL splitViewShouldBeHidden = NO;
155

156 157
    [self setDelegate:self];
    [self setRestorable:NO];
158 159 160
    // TODO: useOptimizedDrawing deprecated since 10.10, but no
    // documentation provided what do use instead.
    // see radar://23047516
161 162 163
    [self useOptimizedDrawing:YES];
    [self setExcludedFromWindowsMenu:YES];
    [self setAcceptsMouseMovedEvents:YES];
164 165
    [self setFrameAutosaveName:@"mainwindow"];

166
    _nativeFullscreenMode = var_InheritBool(getIntf(), "macosx-nativefullscreenmode");
167
    b_dropzone_active = YES;
168

169
    // Playlist setup
170
    VLCPlaylist *playlist = [[VLCMain sharedInstance] playlist];
171 172
    [playlist setOutlineView:(VLCPlaylistView *)_outlineView];
    [playlist setPlaylistHeaderView:_outlineView.headerView];
173
    [self setNextResponder:playlist];
174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217

    // (Re)load sidebar for the first time and select first item
    [self reloadSidebar];
    [_sidebarView selectRowIndexes:[NSIndexSet indexSetWithIndex:1] byExtendingSelection:NO];

    // Setup view frame sizes
    [_dropzoneView setFrame:_playlistScrollView.frame];
    [_splitViewLeft setFrame:_sidebarView.frame];

    /*
     * Set up translatable strings for the UI elements
     */

    // Window title
    [self setTitle:_NS("VLC media player")];

    // Search Field
    [_searchField setToolTip:_NS("Search in Playlist")];
    [_searchField.cell setPlaceholderString:_NS("Search")];
    [_searchField.cell accessibilitySetOverrideValue:_NS("Enter a term to search the playlist. Results will be selected in the table.")
                                        forAttribute:NSAccessibilityDescriptionAttribute];

    // Dropzone
    [_dropzoneLabel setStringValue:_NS("Drop media here")];
    [_dropzoneImageView setImage:imageFromRes(@"dropzone")];
    [_dropzoneButton setTitle:_NS("Open media...")];
    [_dropzoneButton.cell accessibilitySetOverrideValue:_NS("Click to open an advanced dialog to select the media to play. You can also drop files here to play.")
                                           forAttribute:NSAccessibilityDescriptionAttribute];

    // Podcast view
    [_podcastAddButton setTitle:_NS("Subscribe")];
    [_podcastRemoveButton setTitle:_NS("Unsubscribe")];

    // Podcast subscribe window
    [_podcastSubscribeTitle setStringValue:_NS("Subscribe to a podcast")];
    [_podcastSubscribeSubtitle setStringValue:_NS("Enter URL of the podcast to subscribe to:")];
    [_podcastSubscribeOkButton setTitle:_NS("Subscribe")];
    [_podcastSubscribeCancelButton setTitle:_NS("Cancel")];

    // Podcast unsubscribe window
    [_podcastUnsubscirbeTitle setStringValue:_NS("Unsubscribe from a podcast")];
    [_podcastUnsubscribeSubtitle setStringValue:_NS("Select the podcast you would like to unsubscribe from:")];
    [_podcastUnsubscribeOkButton setTitle:_NS("Unsubscribe")];
    [_podcastUnsubscribeCancelButton setTitle:_NS("Cancel")];
218

219
    /* interface builder action */
220
    CGFloat f_threshold_height = f_min_video_height + [self.controlsBar height];
221
    if (self.darkInterface)
222
        f_threshold_height += [self.titlebarView frame].size.height;
223
    if ([[self contentView] frame].size.height < f_threshold_height)
224
        splitViewShouldBeHidden = YES;
225

226
    // Set that here as IB seems to be buggy
227
    if (self.darkInterface)
228
        [self setContentMinSize:NSMakeSize(604., f_min_window_height + [self.titlebarView frame].size.height)];
229
    else
230
        [self setContentMinSize:NSMakeSize(604., f_min_window_height)];
231

232 233
    /* the default small size of the search field is slightly different on Lion, let's work-around that */
    NSRect frame;
234
    frame = [_searchField frame];
235 236
    frame.origin.y = frame.origin.y + 2.0;
    frame.size.height = frame.size.height - 1.0;
237
    [_searchField setFrame:frame];
238

239 240
    _fspanel = [[VLCFSPanelController alloc] init];
    [_fspanel showWindow:self];
241

242
    /* make sure we display the desired default appearance when VLC launches for the first time */
243
    if (![defaults objectForKey:@"VLCFirstRun"]) {
244 245
        [defaults setObject:[NSDate date] forKey:@"VLCFirstRun"];

246
        [_sidebarView expandItem:nil expandChildren:YES];
247

Felix Paul Kühne's avatar
Felix Paul Kühne committed
248
        NSAlert *albumArtAlert = [NSAlert alertWithMessageText:_NS("Check for album art and metadata?") defaultButton:_NS("Enable Metadata Retrieval") alternateButton:_NS("No, Thanks") otherButton:nil informativeTextWithFormat:@"%@",_NS("VLC can check online for album art and metadata to enrich your playback experience, e.g. by providing track information when playing Audio CDs. To provide this functionality, VLC will send information about your contents to trusted services in an anonymized form.")];
249
        NSInteger returnValue = [albumArtAlert runModal];
250
        config_PutInt(getIntf(), "metadata-network-access", returnValue == NSAlertDefaultReturn);
251 252
    }

253
    if (self.darkInterface) {
Felix Paul Kühne's avatar
Felix Paul Kühne committed
254 255
        [defaultCenter addObserver: self selector: @selector(windowResizedOrMoved:) name: NSWindowDidResizeNotification object: nil];
        [defaultCenter addObserver: self selector: @selector(windowResizedOrMoved:) name: NSWindowDidMoveNotification object: nil];
256

257 258
        [self setBackgroundColor: [NSColor clearColor]];
        [self setOpaque: NO];
259 260
        [self display];
        [self setHasShadow:NO];
261
        [self setHasShadow:YES];
262

263
        self.previousSavedFrame = [self frame];
264

265 266
        o_color_backdrop = [[VLCColorView alloc] initWithFrame:_splitView.frame];
        [[self contentView] addSubview:o_color_backdrop positioned:NSWindowBelow relativeTo:_splitView];
267
        [o_color_backdrop setAutoresizingMask:NSViewHeightSizable | NSViewWidthSizable];
268
    } else {
269 270 271
        [self.videoView setFrame:_splitView.frame];
        [_playlistScrollView setBorderType:NSNoBorder];
        [_sidebarScrollView setBorderType:NSNoBorder];
272
    }
273

Felix Paul Kühne's avatar
Felix Paul Kühne committed
274 275 276
    [defaultCenter addObserver: self selector: @selector(someWindowWillClose:) name: NSWindowWillCloseNotification object: nil];
    [defaultCenter addObserver: self selector: @selector(someWindowWillMiniaturize:) name: NSWindowWillMiniaturizeNotification object:nil];
    [defaultCenter addObserver: self selector: @selector(applicationWillTerminate:) name: NSApplicationWillTerminateNotification object: nil];
277
    [defaultCenter addObserver: self selector: @selector(mainSplitViewDidResizeSubviews:) name: NSSplitViewDidResizeSubviewsNotification object:_splitView];
278

279 280
    if (splitViewShouldBeHidden) {
        [self hideSplitView:YES];
281
        f_lastSplitViewHeight = 300;
282
    }
283 284

    /* sanity check for the window size */
Felix Paul Kühne's avatar
Felix Paul Kühne committed
285
    frame = [self frame];
286 287
    NSSize screenSize = [[self screen] frame].size;
    if (screenSize.width <= frame.size.width || screenSize.height <= frame.size.height) {
288
        self.nativeVideoSize = screenSize;
289 290
        [self resizeWindow];
    }
291

292
    /* update fs button to reflect state for next startup */
293
    if (var_InheritBool(pl_Get(getIntf()), "fullscreen"))
294
        [self.controlsBar setFullscreenState:YES];
295 296

    /* restore split view */
297
    f_lastLeftSplitViewWidth = 200;
298
    /* trick NSSplitView implementation, which pretends to know better than us */
299
    if (!config_GetInt(getIntf(), "macosx-show-sidebar"))
300
        [self performSelector:@selector(toggleLeftSubSplitView) withObject:nil afterDelay:0.05];
301 302
}

303
#pragma mark -
304
#pragma mark appearance management
305

306 307 308
- (void)reloadSidebar
{
    BOOL isAReload = NO;
309
    if (o_sidebaritems)
310 311 312 313 314
        isAReload = YES;

    o_sidebaritems = [[NSMutableArray alloc] init];
    SideBarItem *libraryItem = [SideBarItem itemWithTitle:_NS("LIBRARY") identifier:@"library"];
    SideBarItem *playlistItem = [SideBarItem itemWithTitle:_NS("Playlist") identifier:@"playlist"];
315
    [playlistItem setIcon: imageFromRes(@"sidebar-playlist")];
316
    SideBarItem *medialibraryItem = [SideBarItem itemWithTitle:_NS("Media Library") identifier:@"medialibrary"];
317
    [medialibraryItem setIcon: imageFromRes(@"sidebar-playlist")];
318 319 320 321 322
    SideBarItem *mycompItem = [SideBarItem itemWithTitle:_NS("MY COMPUTER") identifier:@"mycomputer"];
    SideBarItem *devicesItem = [SideBarItem itemWithTitle:_NS("DEVICES") identifier:@"devices"];
    SideBarItem *lanItem = [SideBarItem itemWithTitle:_NS("LOCAL NETWORK") identifier:@"localnetwork"];
    SideBarItem *internetItem = [SideBarItem itemWithTitle:_NS("INTERNET") identifier:@"internet"];

323
    /* SD subnodes, inspired by the Qt intf */
324 325
    char **ppsz_longnames = NULL;
    int *p_categories = NULL;
326
    char **ppsz_names = vlc_sd_GetNames(pl_Get(getIntf()), &ppsz_longnames, &p_categories);
327
    if (!ppsz_names)
328
        msg_Err(getIntf(), "no sd item found"); //TODO
329 330 331 332 333 334 335 336
    char **ppsz_name = ppsz_names, **ppsz_longname = ppsz_longnames;
    int *p_category = p_categories;
    NSMutableArray *internetItems = [[NSMutableArray alloc] init];
    NSMutableArray *devicesItems = [[NSMutableArray alloc] init];
    NSMutableArray *lanItems = [[NSMutableArray alloc] init];
    NSMutableArray *mycompItems = [[NSMutableArray alloc] init];
    NSString *o_identifier;
    for (; ppsz_name && *ppsz_name; ppsz_name++, ppsz_longname++, p_category++) {
337
        o_identifier = toNSStr(*ppsz_name);
338 339 340
        switch (*p_category) {
            case SD_CAT_INTERNET:
                [internetItems addObject: [SideBarItem itemWithTitle: _NS(*ppsz_longname) identifier: o_identifier]];
341
                [[internetItems lastObject] setIcon: imageFromRes(@"sidebar-podcast")];
342
                [[internetItems lastObject] setSdtype: SD_CAT_INTERNET];
343
                [[internetItems lastObject] setUntranslatedTitle: toNSStr(*ppsz_longname)];
344 345 346
                break;
            case SD_CAT_DEVICES:
                [devicesItems addObject: [SideBarItem itemWithTitle: _NS(*ppsz_longname) identifier: o_identifier]];
347
                [[devicesItems lastObject] setIcon: imageFromRes(@"sidebar-local")];
348
                [[devicesItems lastObject] setSdtype: SD_CAT_DEVICES];
349
                [[devicesItems lastObject] setUntranslatedTitle: toNSStr(*ppsz_longname)];
350 351 352
                break;
            case SD_CAT_LAN:
                [lanItems addObject: [SideBarItem itemWithTitle: _NS(*ppsz_longname) identifier: o_identifier]];
353
                [[lanItems lastObject] setIcon: imageFromRes(@"sidebar-local")];
354
                [[lanItems lastObject] setSdtype: SD_CAT_LAN];
355
                [[lanItems lastObject] setUntranslatedTitle: toNSStr(*ppsz_longname)];
356 357 358 359
                break;
            case SD_CAT_MYCOMPUTER:
                [mycompItems addObject: [SideBarItem itemWithTitle: _NS(*ppsz_longname) identifier: o_identifier]];
                if (!strncmp(*ppsz_name, "video_dir", 9))
360
                    [[mycompItems lastObject] setIcon: imageFromRes(@"sidebar-movie")];
361
                else if (!strncmp(*ppsz_name, "audio_dir", 9))
362
                    [[mycompItems lastObject] setIcon: imageFromRes(@"sidebar-music")];
363
                else if (!strncmp(*ppsz_name, "picture_dir", 11))
364
                    [[mycompItems lastObject] setIcon: imageFromRes(@"sidebar-pictures")];
365 366
                else
                    [[mycompItems lastObject] setIcon: [NSImage imageNamed:@"NSApplicationIcon"]];
367
                [[mycompItems lastObject] setUntranslatedTitle: toNSStr(*ppsz_longname)];
368 369 370
                [[mycompItems lastObject] setSdtype: SD_CAT_MYCOMPUTER];
                break;
            default:
371
                msg_Warn(getIntf(), "unknown SD type found, skipping (%s)", *ppsz_name);
372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396
                break;
        }

        free(*ppsz_name);
        free(*ppsz_longname);
    }
    [mycompItem setChildren: [NSArray arrayWithArray: mycompItems]];
    [devicesItem setChildren: [NSArray arrayWithArray: devicesItems]];
    [lanItem setChildren: [NSArray arrayWithArray: lanItems]];
    [internetItem setChildren: [NSArray arrayWithArray: internetItems]];
    free(ppsz_names);
    free(ppsz_longnames);
    free(p_categories);

    [libraryItem setChildren: [NSArray arrayWithObjects:playlistItem, medialibraryItem, nil]];
    [o_sidebaritems addObject: libraryItem];
    if ([mycompItem hasChildren])
        [o_sidebaritems addObject: mycompItem];
    if ([devicesItem hasChildren])
        [o_sidebaritems addObject: devicesItem];
    if ([lanItem hasChildren])
        [o_sidebaritems addObject: lanItem];
    if ([internetItem hasChildren])
        [o_sidebaritems addObject: internetItem];

397 398 399
    [_sidebarView reloadData];
    [_sidebarView setDropItem:playlistItem dropChildIndex:NSOutlineViewDropOnItemIndex];
    [_sidebarView registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, @"VLCPlaylistItemPboardType", nil]];
400

401 402 403 404
    [_sidebarView setAutosaveName:@"mainwindow-sidebar"];
    [_sidebarView setDataSource:self];
    [_sidebarView setDelegate:self];
    [_sidebarView setAutosaveExpandedItems:YES];
405

406
    [_sidebarView expandItem:libraryItem expandChildren:YES];
407 408

    if (isAReload) {
409
        [_sidebarView expandItem:nil expandChildren:YES];
410 411 412
    }
}

413 414
- (void)resizePlaylistAfterCollapse
{
415
    // no animation here since we might be in the middle of another resize animation
416
    NSRect rightSplitRect = [_splitViewRight frame];
417

418
    NSRect plrect;
419 420 421
    plrect.size.height = rightSplitRect.size.height - 20.0; // actual pl top bar height, which differs from its frame
    plrect.size.width = rightSplitRect.size.width;
    plrect.origin.x = plrect.origin.y = 0.;
422

423
    NSRect dropzoneboxRect = _dropzoneBox.frame;
424 425 426
    dropzoneboxRect.origin.x = (plrect.size.width - dropzoneboxRect.size.width) / 2;
    dropzoneboxRect.origin.y = (plrect.size.height - dropzoneboxRect.size.height) / 2;

427 428
    [_dropzoneView setFrame:plrect];
    [_dropzoneBox setFrame:dropzoneboxRect];
429 430

    if (b_podcastView_displayed) {
431 432
        plrect.size.height -= [_podcastView frame].size.height;
        plrect.origin.y = [_podcastView frame].size.height;
433
    }
434
    [_playlistScrollView setFrame:plrect];
435

436 437
    [_dropzoneView setNeedsDisplay:YES];
    [_playlistScrollView setNeedsDisplay:YES];
438 439
}

440 441
- (void)makeSplitViewVisible
{
442
    if (self.darkInterface)
443
        [self setContentMinSize: NSMakeSize(604., f_min_window_height + [self.titlebarView frame].size.height)];
444
    else
445
        [self setContentMinSize: NSMakeSize(604., f_min_window_height)];
446 447

    NSRect old_frame = [self frame];
448
    CGFloat newHeight = [self minSize].height;
449
    if (old_frame.size.height < newHeight) {
450 451 452 453
        NSRect new_frame = old_frame;
        new_frame.origin.y = old_frame.origin.y + old_frame.size.height - newHeight;
        new_frame.size.height = newHeight;

454
        [[self animator] setFrame:new_frame display:YES animate:YES];
455 456
    }

457 458
    [self.videoView setHidden:YES];
    [_splitView setHidden:NO];
459
    if (self.nativeFullscreenMode && [self fullscreen]) {
460
        [[self.controlsBar bottomBarView] setHidden:NO];
461
        [self.fspanel setNonActive];
462 463
    }

464
    [self makeFirstResponder:_playlistScrollView];
465 466 467 468
}

- (void)makeSplitViewHidden
{
469
    if (self.darkInterface)
470
        [self setContentMinSize: NSMakeSize(604., f_min_video_height + [self.titlebarView frame].size.height)];
471
    else
472
        [self setContentMinSize: NSMakeSize(604., f_min_video_height)];
473

474 475
    [_splitView setHidden:YES];
    [self.videoView setHidden:NO];
476
    if (self.nativeFullscreenMode && [self fullscreen]) {
477
        [[self.controlsBar bottomBarView] setHidden:YES];
478
        [self.fspanel setActive];
479
    }
480

481
    if ([[self.videoView subviews] count] > 0)
482
        [self makeFirstResponder: [[self.videoView subviews] firstObject]];
483 484
}

485 486

- (void)changePlaylistState:(VLCPlaylistStateEvent)event
487
{
488 489
    // Beware, this code is really ugly

490
    msg_Dbg(getIntf(), "toggle playlist from state: removed splitview %i, minimized view %i. Event %i", b_splitview_removed, b_minimized_view, event);
491 492
    if (![self isVisible] && event == psUserMenuEvent) {
        [self makeKeyAndOrderFront: nil];
493
        return;
494 495
    }

496
    BOOL b_activeVideo = [[VLCMain sharedInstance] activeVideoPlayback];
497
    BOOL b_restored = NO;
498

499
    // ignore alt if triggered through main menu shortcut
500
    BOOL b_have_alt_key = ([[NSApp currentEvent] modifierFlags] & NSAlternateKeyMask) != 0;
501
    if (event == psUserMenuEvent)
502 503
        b_have_alt_key = NO;

504 505 506 507
    // eUserMenuEvent is now handled same as eUserEvent
    if(event == psUserMenuEvent)
        event = psUserEvent;

508
    if (b_dropzone_active && b_have_alt_key) {
509 510 511 512
        [self hideDropZone];
        return;
    }

513 514
    if (!(self.nativeFullscreenMode && self.fullscreen) && !b_splitview_removed && ((b_have_alt_key && b_activeVideo)
                                                                              || (self.nonembedded && event == psUserEvent)
515 516
                                                                              || (!b_activeVideo && event == psUserEvent)
                                                                              || (b_minimized_view && event == psVideoStartedOrStoppedEvent))) {
517 518
        // for starting playback, window is resized through resized events
        // for stopping playback, resize through reset to previous frame
519
        [self hideSplitView: event != psVideoStartedOrStoppedEvent];
520 521
        b_minimized_view = NO;
    } else {
522
        if (b_splitview_removed) {
523
            if (!self.nonembedded || (event == psUserEvent && self.nonembedded))
524
                [self showSplitView: event != psVideoStartedOrStoppedEvent];
525

526
            if (event != psUserEvent)
527 528 529 530 531 532
                b_minimized_view = YES;
            else
                b_minimized_view = NO;

            if (b_activeVideo)
                b_restored = YES;
533 534
        }

535 536
        if (!self.nonembedded) {
            if (([self.videoView isHidden] && b_activeVideo) || b_restored || (b_activeVideo && event != psUserEvent))
537
                [self makeSplitViewHidden];
538
            else
539
                [self makeSplitViewVisible];
540
        } else {
541 542
            [_splitView setHidden: NO];
            [_playlistScrollView setHidden: NO];
543
            [self.videoView setHidden: YES];
544
        }
545
    }
546

547
    msg_Dbg(getIntf(), "toggle playlist to state: removed splitview %i, minimized view %i", b_splitview_removed, b_minimized_view);
548 549
}

550 551 552 553 554
- (IBAction)dropzoneButtonAction:(id)sender
{
    [[[VLCMain sharedInstance] open] openFileGeneric];
}

555 556 557
#pragma mark -
#pragma mark overwritten default functionality

558 559
- (void)windowResizedOrMoved:(NSNotification *)notification
{
560
    [self saveFrameUsingName:[self frameAutosaveName]];
561 562
}

563 564
- (void)applicationWillTerminate:(NSNotification *)notification
{
565
    config_PutInt(getIntf(), "macosx-show-sidebar", ![_splitView isSubviewCollapsed:_splitViewLeft]);
566
    [self saveFrameUsingName:[self frameAutosaveName]];
567 568
}

569

570 571
- (void)someWindowWillClose:(NSNotification *)notification
{
572
    id obj = [notification object];
573

574 575
    // hasActiveVideo is defined for VLCVideoWindowCommon and subclasses
    if ([obj respondsToSelector:@selector(hasActiveVideo)] && [obj hasActiveVideo]) {
576 577 578
        if ([[VLCMain sharedInstance] activeVideoPlayback])
            [[VLCCoreInteraction sharedInstance] stop];
    }
579 580 581 582
}

- (void)someWindowWillMiniaturize:(NSNotification *)notification
{
583
    if (config_GetInt(getIntf(), "macosx-pause-minimized")) {
584
        id obj = [notification object];
585

586
        if ([obj class] == [VLCVideoWindowCommon class] || [obj class] == [VLCDetachedVideoWindow class] || ([obj class] == [VLCMainWindow class] && !self.nonembedded)) {
587
            if ([[VLCMain sharedInstance] activeVideoPlayback])
588 589
                [[VLCCoreInteraction sharedInstance] pause];
        }
590 591 592
    }
}

593 594
#pragma mark -
#pragma mark Update interface and respond to foreign events
595 596
- (void)showDropZone
{
597
    b_dropzone_active = YES;
598
    [_dropzoneView setHidden:NO];
599
    [_playlistScrollView setHidden:YES];
600 601 602 603
}

- (void)hideDropZone
{
604
    b_dropzone_active = NO;
605
    [_dropzoneView setHidden:YES];
606
    [_playlistScrollView setHidden:NO];
607 608
}

609
- (void)hideSplitView:(BOOL)resize
610
{
611 612 613
    // cancel pending pl resizes, in case of fast toggle between both modes
    [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(resizePlaylistAfterCollapse) object:nil];

614
    if (resize) {
615
        NSRect winrect = [self frame];
616
        f_lastSplitViewHeight = [_splitView frame].size.height;
617 618
        winrect.size.height = winrect.size.height - f_lastSplitViewHeight;
        winrect.origin.y = winrect.origin.y + f_lastSplitViewHeight;
619
        [self setFrame:winrect display:YES animate:YES];
620 621
    }

622
    if (self.darkInterface) {
623 624
        [self setContentMinSize: NSMakeSize(604., [self.controlsBar height] + [self.titlebarView frame].size.height)];
        [self setContentMaxSize: NSMakeSize(FLT_MAX, [self.controlsBar height] + [self.titlebarView frame].size.height)];
625
    } else {
626 627
        [self setContentMinSize: NSMakeSize(604., [self.controlsBar height])];
        [self setContentMaxSize: NSMakeSize(FLT_MAX, [self.controlsBar height])];
628
    }
629

630 631 632
    b_splitview_removed = YES;
}

633
- (void)showSplitView:(BOOL)resize
634
{
635
    [self updateWindow];
636
    if (self.darkInterface)
637
        [self setContentMinSize:NSMakeSize(604., f_min_window_height + [self.titlebarView frame].size.height)];
638
    else
639
        [self setContentMinSize:NSMakeSize(604., f_min_window_height)];
640
    [self setContentMaxSize: NSMakeSize(FLT_MAX, FLT_MAX)];
641

642
    if (resize) {
643 644
        NSRect winrect;
        winrect = [self frame];
645 646
        winrect.size.height = winrect.size.height + f_lastSplitViewHeight;
        winrect.origin.y = winrect.origin.y - f_lastSplitViewHeight;
647
        [self setFrame:winrect display:YES animate:YES];
648
    }
649

650 651
    // cancel pending pl resizes, in case of fast toggle between both modes
    [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(resizePlaylistAfterCollapse) object:nil];
652 653 654 655 656
    [self performSelector:@selector(resizePlaylistAfterCollapse) withObject: nil afterDelay:0.75];

    b_splitview_removed = NO;
}

657 658
- (void)updateTimeSlider
{
659 660
    [self.controlsBar updateTimeSlider];
    [self.fspanel updatePositionAndTime];
661

662 663 664
    [[[VLCMain sharedInstance] voutController] updateControlsBarsUsingBlock:^(VLCControlsBarCommon *controlsBar) {
        [controlsBar updateTimeSlider];
    }];
665 666

    [[VLCCoreInteraction sharedInstance] updateAtoB];
667 668
}

669
- (void)updateName
670
{
671
    input_thread_t *p_input;
672
    p_input = pl_CurrentInput(getIntf());
673
    if (p_input) {
674
        NSString *aString = @"";
675

676 677
        if (!config_GetPsz(getIntf(), "video-title")) {
            char *format = var_InheritString(getIntf(), "input-title-format");
678
            if (format) {
679
                char *formated = vlc_strfinput(p_input, format);
680 681 682 683
                free(format);
                aString = toNSStr(formated);
                free(formated);
            }
684
        } else
685
            aString = toNSStr(config_GetPsz(getIntf(), "video-title"));
686

687
        char *uri = input_item_GetURI(input_GetItem(p_input));
688

689
        NSURL * o_url = [NSURL URLWithString:toNSStr(uri)];
690
        if ([o_url isFileURL]) {
691
            [self setRepresentedURL: o_url];
692 693 694
            [[[VLCMain sharedInstance] voutController] updateWindowsUsingBlock:^(VLCVideoWindowCommon *o_window) {
                [o_window setRepresentedURL:o_url];
            }];
695
        } else {
696
            [self setRepresentedURL: nil];
697 698 699
            [[[VLCMain sharedInstance] voutController] updateWindowsUsingBlock:^(VLCVideoWindowCommon *o_window) {
                [o_window setRepresentedURL:nil];
            }];
700
        }
701
        free(uri);
702

703
        if ([aString isEqualToString:@""]) {
704 705 706 707
            if ([o_url isFileURL])
                aString = [[NSFileManager defaultManager] displayNameAtPath: [o_url path]];
            else
                aString = [o_url absoluteString];
708
        }
709

710 711 712 713 714 715
        if ([aString length] > 0) {
            [self setTitle: aString];
            [[[VLCMain sharedInstance] voutController] updateWindowsUsingBlock:^(VLCVideoWindowCommon *o_window) {
                [o_window setTitle:aString];
            }];

716
            [self.fspanel setStreamTitle: aString];
717
        } else {
718
            [self setTitle: _NS("VLC media player")];
719 720
            [self setRepresentedURL: nil];
        }
721

722 723
        vlc_object_release(p_input);
    } else {
724
        [self setTitle: _NS("VLC media player")];
725
        [self setRepresentedURL: nil];
726
    }
727 728
}

729 730
- (void)updateWindow
{
731
    [self.controlsBar updateControls];
732 733 734
    [[[VLCMain sharedInstance] voutController] updateControlsBarsUsingBlock:^(VLCControlsBarCommon *controlsBar) {
        [controlsBar updateControls];
    }];
735

736 737
    bool b_seekable = false;

738
    playlist_t *p_playlist = pl_Get(getIntf());
739
    input_thread_t *p_input = playlist_CurrentInput(p_playlist);
740
    if (p_input) {
741
        /* seekable streams */
742
        b_seekable = var_GetBool(p_input, "can-seek");
743

744
        vlc_object_release(p_input);
745 746
    }

747
    [self updateTimeSlider];
748 749
    if ([self.fspanel respondsToSelector:@selector(setSeekable:)])
        [self.fspanel setSeekable: b_seekable];
750 751

    PL_LOCK;
752 753
    if ([[[[VLCMain sharedInstance] playlist] model] currentRootType] != ROOT_TYPE_PLAYLIST ||
        [[[[VLCMain sharedInstance] playlist] model] hasChildren])
754 755 756 757
        [self hideDropZone];
    else
        [self showDropZone];
    PL_UNLOCK;
758
    [_sidebarView setNeedsDisplay:YES];
759 760

    [self _updatePlaylistTitle];
761 762 763 764
}

- (void)setPause
{
765 766
    [self.controlsBar setPause];
    [self.fspanel setPause];
767

768 769 770
    [[[VLCMain sharedInstance] voutController] updateControlsBarsUsingBlock:^(VLCControlsBarCommon *controlsBar) {
        [controlsBar setPause];
    }];
771 772 773 774
}

- (void)setPlay
{
775 776
    [self.controlsBar setPlay];
    [self.fspanel setPlay];
777

778 779 780
    [[[VLCMain sharedInstance] voutController] updateControlsBarsUsingBlock:^(VLCControlsBarCommon *controlsBar) {
        [controlsBar setPlay];
    }];
781 782
}

783
- (void)updateVolumeSlider
784
{
785
    [(VLCMainWindowControlsBar *)[self controlsBar] updateVolumeSlider];
786
    [self.fspanel setVolumeLevel:[[VLCCoreInteraction sharedInstance] volume]];
787 788
}

789 790
#pragma mark -
#pragma mark Video Output handling
791

792 793
- (void)videoplayWillBeStarted
{
794
    if (!self.fullscreen)
795 796 797
        frameBeforePlayback = [self frame];
}

798
- (void)setVideoplayEnabled
799
{
800
    BOOL b_videoPlayback = [[VLCMain sharedInstance] activeVideoPlayback];
801 802
        
    if (!b_videoPlayback) {
803
        if (!self.nonembedded && (!self.nativeFullscreenMode || (self.nativeFullscreenMode && !self.fullscreen)) && frameBeforePlayback.size.width > 0 && frameBeforePlayback.size.height > 0) {
804 805

            // only resize back to minimum view of this is still desired final state
806
            CGFloat f_threshold_height = f_min_video_height + [self.controlsBar height];
807
            if(frameBeforePlayback.size.height > f_threshold_height || b_minimized_view) {
808 809 810 811 812 813

                if ([[VLCMain sharedInstance] isTerminating])
                    [self setFrame:frameBeforePlayback display:YES];
                else
                    [[self animator] setFrame:frameBeforePlayback display:YES];

814
            }
815 816 817
        }

        frameBeforePlayback = NSMakeRect(0, 0, 0, 0);
818

819
        // update fs button to reflect state for next startup
820
        if (var_InheritBool(getIntf(), "fullscreen") || var_GetBool(pl_Get(getIntf()), "fullscreen")) {
821
            [self.controlsBar setFullscreenState:YES];
822 823
        }

824
        [self makeFirstResponder: _playlistScrollView];
825
        [[[VLCMain sharedInstance] voutController] updateWindowLevelForHelperWindows: NSNormalWindowLevel];
826

827 828 829
        // restore alpha value to 1 for the case that macosx-opaqueness is set to < 1
        [self setAlphaValue:1.0];
    }
830

831
    if (self.nativeFullscreenMode) {
832
        if ([self hasActiveVideo] && [self fullscreen]) {
833
            [[self.controlsBar bottomBarView] setHidden: b_videoPlayback];
834
            [self.fspanel setActive];
835
        } else {
836
            [[self.controlsBar bottomBarView] setHidden: NO];
837
            [self.fspanel setNonActive];
838
        }
839
    }
840
}
841

842 843 844 845 846
#pragma mark -
#pragma mark Lion native fullscreen handling
- (void)windowWillEnterFullScreen:(NSNotification *)notification
{
    [super windowWillEnterFullScreen:notification];
847

848
    // update split view frame after removing title bar
849
    if (self.darkInterface) {
850
        NSRect frame = [[self contentView] frame];
851 852
        frame.origin.y += [self.controlsBar height];
        frame.size.height -= [self.controlsBar height];
853
        [_splitView setFrame:frame];
854
    }
855 856 857 858 859 860 861
}

- (void)windowWillExitFullScreen:(NSNotification *)notification
{
    [super windowWillExitFullScreen: notification];

    // update split view frame after readding title bar
862
    if (self.darkInterface) {
863
        NSRect frame = [_splitView frame];
864
        frame.size.height -= [self.titlebarView frame].size.height;
865
        [_splitView setFrame:frame];
866
    }
867
}
868 869
#pragma mark -
#pragma mark Fullscreen support
870

871 872
- (void)showFullscreenController
{
873 874
    id currentWindow = [NSApp keyWindow];
    if ([currentWindow respondsToSelector:@selector(hasActiveVideo)] && [currentWindow hasActiveVideo]) {
875
        if ([currentWindow respondsToSelector:@selector(fullscreen)] && [currentWindow fullscreen] && ![[currentWindow videoView] isHidden]) {
876 877

            if ([[VLCMain sharedInstance] activeVideoPlayback])
878
                [self.fspanel fadeIn];
879 880
        }
    }
881

882 883
}

884
#pragma mark -
885 886 887 888
#pragma mark split view delegate
- (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMax ofSubviewAt:(NSInteger)dividerIndex
{
    if (dividerIndex == 0)
889
        return 300.;
890 891 892 893
    else
        return proposedMax;
}

894 895 896 897 898 899 900 901
- (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMin ofSubviewAt:(NSInteger)dividerIndex
{
    if (dividerIndex == 0)
        return 100.;
    else
        return proposedMin;
}

902 903
- (BOOL)splitView:(NSSplitView *)splitView canCollapseSubview:(NSView *)subview
{
904
    return ([subview isEqual:_splitViewLeft]);
905 906
}

907 908
- (BOOL)splitView:(NSSplitView *)splitView shouldAdjustSizeOfSubview:(NSView *)subview
{
909
    return (![subview isEqual:_splitViewLeft]);
910 911
}

912
- (void)mainSplitViewDidResizeSubviews:(id)object
913
{
914
    f_lastLeftSplitViewWidth = [_splitViewLeft frame].size.width;
915
    config_PutInt(getIntf(), "macosx-show-sidebar", ![_splitView isSubviewCollapsed:_splitViewLeft]);
916 917 918 919 920
    [[[VLCMain sharedInstance] mainMenu] updateSidebarMenuItem];
}

- (void)toggleLeftSubSplitView
{
921 922 923
    [_splitView adjustSubviews];
    if ([_splitView isSubviewCollapsed:_splitViewLeft])
        [_splitView setPosition:f_lastLeftSplitViewWidth ofDividerAtIndex:0];
924
    else
925
        [_splitView setPosition:[_splitView minPossiblePositionOfDividerAtIndex:0] ofDividerAtIndex:0];
926
    [[[VLCMain sharedInstance] mainMenu] updateSidebarMenuItem];
927 928
}

929 930 931 932
#pragma mark -
#pragma mark private playlist magic
- (void)_updatePlaylistTitle
{
933
    PLRootType root = [[[[VLCMain sharedInstance] playlist] model] currentRootType];
934
    playlist_t *p_playlist = pl_Get(getIntf());
935

936 937
    PL_LOCK;
    if (root == ROOT_TYPE_PLAYLIST)
938
        [_categoryLabel setStringValue: [_NS("Playlist") stringByAppendingString:[self _playbackDurationOfNode:p_playlist->p_playing]]];
939
    else if (root == ROOT_TYPE_MEDIALIBRARY)
940
        [_categoryLabel setStringValue: [_NS("Media Library") stringByAppendingString:[self _playbackDurationOfNode:p_playlist->p_media_library]]];
941 942

    PL_UNLOCK;
943 944 945 946 947 948 949
}

- (NSString *)_playbackDurationOfNode:(playlist_item_t*)node
{
    if (!node)
        return @"";

950
    playlist_t * p_playlist = pl_Get(getIntf());
951 952
    PL_ASSERT_LOCKED;

953 954 955 956 957 958 959 960 961
    mtime_t mt_duration = playlist_GetNodeDuration( node );

    if (mt_duration < 1)
        return @"";

    mt_duration = mt_duration / 1000000;

    NSDate *date = [NSDate dateWithTimeIntervalSince1970:mt_duration];
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
962 963 964 965 966
    if (mt_duration >= 86400) {
        [formatter setDateFormat:@"dd:HH:mm:ss"];
    } else {
        [formatter setDateFormat:@"HH:mm:ss"];
    }
967 968
    [formatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];

969
    return [NSString stringWithFormat:@" — %@",[formatter stringFromDate:date]];
970 971
}

972 973
- (IBAction)searchItem:(id)sender
{
974
    [[[[VLCMain sharedInstance] playlist] model] searchUpdate:[_searchField stringValue]];
975 976
}

977 978 979 980 981
- (IBAction)highlightSearchField:(id)sender
{
    [_searchField selectText:sender];
}

982
#pragma mark -
983 984 985 986
#pragma mark Side Bar Data handling
/* taken under BSD-new from the PXSourceList sample project, adapted for VLC */
- (NSUInteger)sourceList:(PXSourceList*)sourceList numberOfChildrenOfItem:(id)item
{
987
    //Works the same way as the NSOutlineView data source: `nil` means a parent item
988
    if (item==nil)
989 990 991
        return [o_sidebaritems count];
    else
        return [[item children] count];
992 993 994 995 996 997
}


- (id)sourceList:(PXSourceList*)aSourceList child:(NSUInteger)index ofItem:(id)item
{
    //Works the same way as the NSOutlineView data source: `nil` means a parent item
998
    if (item==nil)
999
        return [o_sidebaritems objectAtIndex:index];
1000
    else
1001
        return [[item children] objectAtIndex:index];
1002 1003 1004 1005 1006
}


- (id)sourceList:(PXSourceList*)aSourceList objectValueForItem:(id)item
{
1007
    return [item title];
1008 1009 1010 1011
}

- (void)sourceList:(PXSourceList*)aSourceList setObjectValue:(id)object forItem:(id)item
{
1012
    [item setTitle:object];
1013 1014 1015 1016
}

- (BOOL)sourceList:(PXSourceList*)aSourceList isItemExpandable:(id)item
{
1017
    return [item hasChildren];
1018 1019 1020 1021 1022
}


- (BOOL)sourceList:(PXSourceList*)aSourceList itemHasBadge:(id)item
{
1023
    if ([[item identifier] isEqualToString: @"playlist"] || [[item identifier