VLCMainWindow.m 50.9 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
        NSRect winrect = [self frame];
264
        CGFloat f_titleBarHeight = [self.titlebarView frame].size.height;
265

266
        [self.titlebarView setFrame: NSMakeRect(0, winrect.size.height - f_titleBarHeight,
267
                                              winrect.size.width, f_titleBarHeight)];
268
        [[self contentView] addSubview: self.titlebarView positioned: NSWindowAbove relativeTo: _splitView];
269

270
        if (winrect.size.height > 100) {
271
            [self setFrame: winrect display:YES animate:YES];
272
            self.previousSavedFrame = winrect;
273
        }
274

275
        winrect = _splitView.frame;
276
        winrect.size.height = winrect.size.height - f_titleBarHeight;
277
        [_splitView setFrame: winrect];
278
        [self.videoView setFrame: winrect];
279

280 281
        o_color_backdrop = [[VLCColorView alloc] initWithFrame:_splitView.frame];
        [[self contentView] addSubview:o_color_backdrop positioned:NSWindowBelow relativeTo:_splitView];
282
        [o_color_backdrop setAutoresizingMask:NSViewHeightSizable | NSViewWidthSizable];
283
    } else {
284 285 286
        [self.videoView setFrame:_splitView.frame];
        [_playlistScrollView setBorderType:NSNoBorder];
        [_sidebarScrollView setBorderType:NSNoBorder];
287
    }
288

Felix Paul Kühne's avatar
Felix Paul Kühne committed
289 290 291
    [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];
292
    [defaultCenter addObserver: self selector: @selector(mainSplitViewDidResizeSubviews:) name: NSSplitViewDidResizeSubviewsNotification object:_splitView];
293

294 295
    if (splitViewShouldBeHidden) {
        [self hideSplitView:YES];
296
        f_lastSplitViewHeight = 300;
297
    }
298 299

    /* sanity check for the window size */
Felix Paul Kühne's avatar
Felix Paul Kühne committed
300
    frame = [self frame];
301 302
    NSSize screenSize = [[self screen] frame].size;
    if (screenSize.width <= frame.size.width || screenSize.height <= frame.size.height) {
303
        self.nativeVideoSize = screenSize;
304 305
        [self resizeWindow];
    }
306

307
    /* update fs button to reflect state for next startup */
308
    if (var_InheritBool(pl_Get(getIntf()), "fullscreen"))
309
        [self.controlsBar setFullscreenState:YES];
310 311

    /* restore split view */
312
    f_lastLeftSplitViewWidth = 200;
313
    /* trick NSSplitView implementation, which pretends to know better than us */
314
    if (!config_GetInt(getIntf(), "macosx-show-sidebar"))
315
        [self performSelector:@selector(toggleLeftSubSplitView) withObject:nil afterDelay:0.05];
316 317
}

318
#pragma mark -
319
#pragma mark appearance management
320

321 322 323
- (void)reloadSidebar
{
    BOOL isAReload = NO;
324
    if (o_sidebaritems)
325 326 327 328 329
        isAReload = YES;

    o_sidebaritems = [[NSMutableArray alloc] init];
    SideBarItem *libraryItem = [SideBarItem itemWithTitle:_NS("LIBRARY") identifier:@"library"];
    SideBarItem *playlistItem = [SideBarItem itemWithTitle:_NS("Playlist") identifier:@"playlist"];
330
    [playlistItem setIcon: imageFromRes(@"sidebar-playlist")];
331
    SideBarItem *medialibraryItem = [SideBarItem itemWithTitle:_NS("Media Library") identifier:@"medialibrary"];
332
    [medialibraryItem setIcon: imageFromRes(@"sidebar-playlist")];
333 334 335 336 337
    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"];

338
    /* SD subnodes, inspired by the Qt intf */
339 340
    char **ppsz_longnames = NULL;
    int *p_categories = NULL;
341
    char **ppsz_names = vlc_sd_GetNames(pl_Get(getIntf()), &ppsz_longnames, &p_categories);
342
    if (!ppsz_names)
343
        msg_Err(getIntf(), "no sd item found"); //TODO
344 345 346 347 348 349 350 351
    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++) {
352
        o_identifier = toNSStr(*ppsz_name);
353 354 355
        switch (*p_category) {
            case SD_CAT_INTERNET:
                [internetItems addObject: [SideBarItem itemWithTitle: _NS(*ppsz_longname) identifier: o_identifier]];
356
                [[internetItems lastObject] setIcon: imageFromRes(@"sidebar-podcast")];
357
                [[internetItems lastObject] setSdtype: SD_CAT_INTERNET];
358
                [[internetItems lastObject] setUntranslatedTitle: toNSStr(*ppsz_longname)];
359 360 361
                break;
            case SD_CAT_DEVICES:
                [devicesItems addObject: [SideBarItem itemWithTitle: _NS(*ppsz_longname) identifier: o_identifier]];
362
                [[devicesItems lastObject] setIcon: imageFromRes(@"sidebar-local")];
363
                [[devicesItems lastObject] setSdtype: SD_CAT_DEVICES];
364
                [[devicesItems lastObject] setUntranslatedTitle: toNSStr(*ppsz_longname)];
365 366 367
                break;
            case SD_CAT_LAN:
                [lanItems addObject: [SideBarItem itemWithTitle: _NS(*ppsz_longname) identifier: o_identifier]];
368
                [[lanItems lastObject] setIcon: imageFromRes(@"sidebar-local")];
369
                [[lanItems lastObject] setSdtype: SD_CAT_LAN];
370
                [[lanItems lastObject] setUntranslatedTitle: toNSStr(*ppsz_longname)];
371 372 373 374
                break;
            case SD_CAT_MYCOMPUTER:
                [mycompItems addObject: [SideBarItem itemWithTitle: _NS(*ppsz_longname) identifier: o_identifier]];
                if (!strncmp(*ppsz_name, "video_dir", 9))
375
                    [[mycompItems lastObject] setIcon: imageFromRes(@"sidebar-movie")];
376
                else if (!strncmp(*ppsz_name, "audio_dir", 9))
377
                    [[mycompItems lastObject] setIcon: imageFromRes(@"sidebar-music")];
378
                else if (!strncmp(*ppsz_name, "picture_dir", 11))
379
                    [[mycompItems lastObject] setIcon: imageFromRes(@"sidebar-pictures")];
380 381
                else
                    [[mycompItems lastObject] setIcon: [NSImage imageNamed:@"NSApplicationIcon"]];
382
                [[mycompItems lastObject] setUntranslatedTitle: toNSStr(*ppsz_longname)];
383 384 385
                [[mycompItems lastObject] setSdtype: SD_CAT_MYCOMPUTER];
                break;
            default:
386
                msg_Warn(getIntf(), "unknown SD type found, skipping (%s)", *ppsz_name);
387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411
                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];

412 413 414
    [_sidebarView reloadData];
    [_sidebarView setDropItem:playlistItem dropChildIndex:NSOutlineViewDropOnItemIndex];
    [_sidebarView registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, @"VLCPlaylistItemPboardType", nil]];
415

416 417 418 419
    [_sidebarView setAutosaveName:@"mainwindow-sidebar"];
    [_sidebarView setDataSource:self];
    [_sidebarView setDelegate:self];
    [_sidebarView setAutosaveExpandedItems:YES];
420

421
    [_sidebarView expandItem:libraryItem expandChildren:YES];
422 423

    if (isAReload) {
424
        [_sidebarView expandItem:nil expandChildren:YES];
425 426 427
    }
}

428 429
- (void)resizePlaylistAfterCollapse
{
430
    // no animation here since we might be in the middle of another resize animation
431
    NSRect rightSplitRect = [_splitViewRight frame];
432

433
    NSRect plrect;
434 435 436
    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.;
437

438
    NSRect dropzoneboxRect = _dropzoneBox.frame;
439 440 441
    dropzoneboxRect.origin.x = (plrect.size.width - dropzoneboxRect.size.width) / 2;
    dropzoneboxRect.origin.y = (plrect.size.height - dropzoneboxRect.size.height) / 2;

442 443
    [_dropzoneView setFrame:plrect];
    [_dropzoneBox setFrame:dropzoneboxRect];
444 445

    if (b_podcastView_displayed) {
446 447
        plrect.size.height -= [_podcastView frame].size.height;
        plrect.origin.y = [_podcastView frame].size.height;
448
    }
449
    [_playlistScrollView setFrame:plrect];
450

451 452
    [_dropzoneView setNeedsDisplay:YES];
    [_playlistScrollView setNeedsDisplay:YES];
453 454
}

455 456
- (void)makeSplitViewVisible
{
457
    if (self.darkInterface)
458
        [self setContentMinSize: NSMakeSize(604., f_min_window_height + [self.titlebarView frame].size.height)];
459
    else
460
        [self setContentMinSize: NSMakeSize(604., f_min_window_height)];
461 462

    NSRect old_frame = [self frame];
463
    CGFloat newHeight = [self minSize].height;
464
    if (old_frame.size.height < newHeight) {
465 466 467 468
        NSRect new_frame = old_frame;
        new_frame.origin.y = old_frame.origin.y + old_frame.size.height - newHeight;
        new_frame.size.height = newHeight;

469
        [[self animator] setFrame:new_frame display:YES animate:YES];
470 471
    }

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

479
    [self makeFirstResponder:_playlistScrollView];
480 481 482 483
}

- (void)makeSplitViewHidden
{
484
    if (self.darkInterface)
485
        [self setContentMinSize: NSMakeSize(604., f_min_video_height + [self.titlebarView frame].size.height)];
486
    else
487
        [self setContentMinSize: NSMakeSize(604., f_min_video_height)];
488

489 490
    [_splitView setHidden:YES];
    [self.videoView setHidden:NO];
491
    if (self.nativeFullscreenMode && [self fullscreen]) {
492
        [[self.controlsBar bottomBarView] setHidden:YES];
493
        [self.fspanel setActive];
494
    }
495

496
    if ([[self.videoView subviews] count] > 0)
497
        [self makeFirstResponder: [[self.videoView subviews] firstObject]];
498 499
}

500 501

- (void)changePlaylistState:(VLCPlaylistStateEvent)event
502
{
503 504
    // Beware, this code is really ugly

505
    msg_Dbg(getIntf(), "toggle playlist from state: removed splitview %i, minimized view %i. Event %i", b_splitview_removed, b_minimized_view, event);
506 507
    if (![self isVisible] && event == psUserMenuEvent) {
        [self makeKeyAndOrderFront: nil];
508
        return;
509 510
    }

511
    BOOL b_activeVideo = [[VLCMain sharedInstance] activeVideoPlayback];
512
    BOOL b_restored = NO;
513

514
    // ignore alt if triggered through main menu shortcut
515
    BOOL b_have_alt_key = ([[NSApp currentEvent] modifierFlags] & NSAlternateKeyMask) != 0;
516
    if (event == psUserMenuEvent)
517 518
        b_have_alt_key = NO;

519 520 521 522
    // eUserMenuEvent is now handled same as eUserEvent
    if(event == psUserMenuEvent)
        event = psUserEvent;

523
    if (b_dropzone_active && b_have_alt_key) {
524 525 526 527
        [self hideDropZone];
        return;
    }

528 529
    if (!(self.nativeFullscreenMode && self.fullscreen) && !b_splitview_removed && ((b_have_alt_key && b_activeVideo)
                                                                              || (self.nonembedded && event == psUserEvent)
530 531
                                                                              || (!b_activeVideo && event == psUserEvent)
                                                                              || (b_minimized_view && event == psVideoStartedOrStoppedEvent))) {
532 533
        // for starting playback, window is resized through resized events
        // for stopping playback, resize through reset to previous frame
534
        [self hideSplitView: event != psVideoStartedOrStoppedEvent];
535 536
        b_minimized_view = NO;
    } else {
537
        if (b_splitview_removed) {
538
            if (!self.nonembedded || (event == psUserEvent && self.nonembedded))
539
                [self showSplitView: event != psVideoStartedOrStoppedEvent];
540

541
            if (event != psUserEvent)
542 543 544 545 546 547
                b_minimized_view = YES;
            else
                b_minimized_view = NO;

            if (b_activeVideo)
                b_restored = YES;
548 549
        }

550 551
        if (!self.nonembedded) {
            if (([self.videoView isHidden] && b_activeVideo) || b_restored || (b_activeVideo && event != psUserEvent))
552
                [self makeSplitViewHidden];
553
            else
554
                [self makeSplitViewVisible];
555
        } else {
556 557
            [_splitView setHidden: NO];
            [_playlistScrollView setHidden: NO];
558
            [self.videoView setHidden: YES];
559
        }
560
    }
561

562
    msg_Dbg(getIntf(), "toggle playlist to state: removed splitview %i, minimized view %i", b_splitview_removed, b_minimized_view);
563 564
}

565 566 567 568 569
- (IBAction)dropzoneButtonAction:(id)sender
{
    [[[VLCMain sharedInstance] open] openFileGeneric];
}

570 571 572
#pragma mark -
#pragma mark overwritten default functionality

573 574
- (void)windowResizedOrMoved:(NSNotification *)notification
{
575
    [self saveFrameUsingName:[self frameAutosaveName]];
576 577
}

578 579
- (void)applicationWillTerminate:(NSNotification *)notification
{
580
    config_PutInt(getIntf(), "macosx-show-sidebar", ![_splitView isSubviewCollapsed:_splitViewLeft]);
581
    [self saveFrameUsingName:[self frameAutosaveName]];
582 583
}

584

585 586
- (void)someWindowWillClose:(NSNotification *)notification
{
587
    id obj = [notification object];
588

589 590
    // hasActiveVideo is defined for VLCVideoWindowCommon and subclasses
    if ([obj respondsToSelector:@selector(hasActiveVideo)] && [obj hasActiveVideo]) {
591 592 593
        if ([[VLCMain sharedInstance] activeVideoPlayback])
            [[VLCCoreInteraction sharedInstance] stop];
    }
594 595 596 597
}

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

601
        if ([obj class] == [VLCVideoWindowCommon class] || [obj class] == [VLCDetachedVideoWindow class] || ([obj class] == [VLCMainWindow class] && !self.nonembedded)) {
602
            if ([[VLCMain sharedInstance] activeVideoPlayback])
603 604
                [[VLCCoreInteraction sharedInstance] pause];
        }
605 606 607
    }
}

608 609
#pragma mark -
#pragma mark Update interface and respond to foreign events
610 611
- (void)showDropZone
{
612
    b_dropzone_active = YES;
613
    [_dropzoneView setHidden:NO];
614
    [_playlistScrollView setHidden:YES];
615 616 617 618
}

- (void)hideDropZone
{
619
    b_dropzone_active = NO;
620
    [_dropzoneView setHidden:YES];
621
    [_playlistScrollView setHidden:NO];
622 623
}

624
- (void)hideSplitView:(BOOL)resize
625
{
626 627 628
    // cancel pending pl resizes, in case of fast toggle between both modes
    [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(resizePlaylistAfterCollapse) object:nil];

629
    if (resize) {
630
        NSRect winrect = [self frame];
631
        f_lastSplitViewHeight = [_splitView frame].size.height;
632 633
        winrect.size.height = winrect.size.height - f_lastSplitViewHeight;
        winrect.origin.y = winrect.origin.y + f_lastSplitViewHeight;
634
        [self setFrame:winrect display:YES animate:YES];
635 636
    }

637
    if (self.darkInterface) {
638 639
        [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)];
640
    } else {
641 642
        [self setContentMinSize: NSMakeSize(604., [self.controlsBar height])];
        [self setContentMaxSize: NSMakeSize(FLT_MAX, [self.controlsBar height])];
643
    }
644

645 646 647
    b_splitview_removed = YES;
}

648
- (void)showSplitView:(BOOL)resize
649
{
650
    [self updateWindow];
651
    if (self.darkInterface)
652
        [self setContentMinSize:NSMakeSize(604., f_min_window_height + [self.titlebarView frame].size.height)];
653
    else
654
        [self setContentMinSize:NSMakeSize(604., f_min_window_height)];
655
    [self setContentMaxSize: NSMakeSize(FLT_MAX, FLT_MAX)];
656

657
    if (resize) {
658 659
        NSRect winrect;
        winrect = [self frame];
660 661
        winrect.size.height = winrect.size.height + f_lastSplitViewHeight;
        winrect.origin.y = winrect.origin.y - f_lastSplitViewHeight;
662
        [self setFrame:winrect display:YES animate:YES];
663
    }
664

665 666
    // cancel pending pl resizes, in case of fast toggle between both modes
    [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(resizePlaylistAfterCollapse) object:nil];
667 668 669 670 671
    [self performSelector:@selector(resizePlaylistAfterCollapse) withObject: nil afterDelay:0.75];

    b_splitview_removed = NO;
}

672 673
- (void)updateTimeSlider
{
674 675
    [self.controlsBar updateTimeSlider];
    [self.fspanel updatePositionAndTime];
676

677 678 679
    [[[VLCMain sharedInstance] voutController] updateControlsBarsUsingBlock:^(VLCControlsBarCommon *controlsBar) {
        [controlsBar updateTimeSlider];
    }];
680 681

    [[VLCCoreInteraction sharedInstance] updateAtoB];
682 683
}

684
- (void)updateName
685
{
686
    input_thread_t *p_input;
687
    p_input = pl_CurrentInput(getIntf());
688
    if (p_input) {
689
        NSString *aString = @"";
690

691 692
        if (!config_GetPsz(getIntf(), "video-title")) {
            char *format = var_InheritString(getIntf(), "input-title-format");
693
            if (format) {
694
                char *formated = vlc_strfinput(p_input, format);
695 696 697 698
                free(format);
                aString = toNSStr(formated);
                free(formated);
            }
699
        } else
700
            aString = toNSStr(config_GetPsz(getIntf(), "video-title"));
701

702
        char *uri = input_item_GetURI(input_GetItem(p_input));
703

704
        NSURL * o_url = [NSURL URLWithString:toNSStr(uri)];
705
        if ([o_url isFileURL]) {
706
            [self setRepresentedURL: o_url];
707 708 709
            [[[VLCMain sharedInstance] voutController] updateWindowsUsingBlock:^(VLCVideoWindowCommon *o_window) {
                [o_window setRepresentedURL:o_url];
            }];
710
        } else {
711
            [self setRepresentedURL: nil];
712 713 714
            [[[VLCMain sharedInstance] voutController] updateWindowsUsingBlock:^(VLCVideoWindowCommon *o_window) {
                [o_window setRepresentedURL:nil];
            }];
715
        }
716
        free(uri);
717

718
        if ([aString isEqualToString:@""]) {
719 720 721 722
            if ([o_url isFileURL])
                aString = [[NSFileManager defaultManager] displayNameAtPath: [o_url path]];
            else
                aString = [o_url absoluteString];
723
        }
724

725 726 727 728 729 730
        if ([aString length] > 0) {
            [self setTitle: aString];
            [[[VLCMain sharedInstance] voutController] updateWindowsUsingBlock:^(VLCVideoWindowCommon *o_window) {
                [o_window setTitle:aString];
            }];

731
            [self.fspanel setStreamTitle: aString];
732
        } else {
733
            [self setTitle: _NS("VLC media player")];
734 735
            [self setRepresentedURL: nil];
        }
736

737 738
        vlc_object_release(p_input);
    } else {
739
        [self setTitle: _NS("VLC media player")];
740
        [self setRepresentedURL: nil];
741
    }
742 743
}

744 745
- (void)updateWindow
{
746
    [self.controlsBar updateControls];
747 748 749
    [[[VLCMain sharedInstance] voutController] updateControlsBarsUsingBlock:^(VLCControlsBarCommon *controlsBar) {
        [controlsBar updateControls];
    }];
750

751 752
    bool b_seekable = false;

753
    playlist_t *p_playlist = pl_Get(getIntf());
754
    input_thread_t *p_input = playlist_CurrentInput(p_playlist);
755
    if (p_input) {
756
        /* seekable streams */
757
        b_seekable = var_GetBool(p_input, "can-seek");
758

759
        vlc_object_release(p_input);
760 761
    }

762
    [self updateTimeSlider];
763 764
    if ([self.fspanel respondsToSelector:@selector(setSeekable:)])
        [self.fspanel setSeekable: b_seekable];
765 766

    PL_LOCK;
767 768
    if ([[[[VLCMain sharedInstance] playlist] model] currentRootType] != ROOT_TYPE_PLAYLIST ||
        [[[[VLCMain sharedInstance] playlist] model] hasChildren])
769 770 771 772
        [self hideDropZone];
    else
        [self showDropZone];
    PL_UNLOCK;
773
    [_sidebarView setNeedsDisplay:YES];
774 775

    [self _updatePlaylistTitle];
776 777 778 779
}

- (void)setPause
{
780 781
    [self.controlsBar setPause];
    [self.fspanel setPause];
782

783 784 785
    [[[VLCMain sharedInstance] voutController] updateControlsBarsUsingBlock:^(VLCControlsBarCommon *controlsBar) {
        [controlsBar setPause];
    }];
786 787 788 789
}

- (void)setPlay
{
790 791
    [self.controlsBar setPlay];
    [self.fspanel setPlay];
792

793 794 795
    [[[VLCMain sharedInstance] voutController] updateControlsBarsUsingBlock:^(VLCControlsBarCommon *controlsBar) {
        [controlsBar setPlay];
    }];
796 797
}

798
- (void)updateVolumeSlider
799
{
800
    [(VLCMainWindowControlsBar *)[self controlsBar] updateVolumeSlider];
801
    [self.fspanel setVolumeLevel:[[VLCCoreInteraction sharedInstance] volume]];
802 803
}

804 805
#pragma mark -
#pragma mark Video Output handling
806

807 808
- (void)videoplayWillBeStarted
{
809
    if (!self.fullscreen)
810 811 812
        frameBeforePlayback = [self frame];
}

813
- (void)setVideoplayEnabled
814
{
815
    BOOL b_videoPlayback = [[VLCMain sharedInstance] activeVideoPlayback];
816 817
        
    if (!b_videoPlayback) {
818
        if (!self.nonembedded && (!self.nativeFullscreenMode || (self.nativeFullscreenMode && !self.fullscreen)) && frameBeforePlayback.size.width > 0 && frameBeforePlayback.size.height > 0) {
819 820

            // only resize back to minimum view of this is still desired final state
821
            CGFloat f_threshold_height = f_min_video_height + [self.controlsBar height];
822
            if(frameBeforePlayback.size.height > f_threshold_height || b_minimized_view) {
823 824 825 826 827 828

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

829
            }
830 831 832
        }

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

834
        // update fs button to reflect state for next startup
835
        if (var_InheritBool(getIntf(), "fullscreen") || var_GetBool(pl_Get(getIntf()), "fullscreen")) {
836
            [self.controlsBar setFullscreenState:YES];
837 838
        }

839
        [self makeFirstResponder: _playlistScrollView];
840
        [[[VLCMain sharedInstance] voutController] updateWindowLevelForHelperWindows: NSNormalWindowLevel];
841

842 843 844
        // restore alpha value to 1 for the case that macosx-opaqueness is set to < 1
        [self setAlphaValue:1.0];
    }
845

846
    if (self.nativeFullscreenMode) {
847
        if ([self hasActiveVideo] && [self fullscreen]) {
848
            [[self.controlsBar bottomBarView] setHidden: b_videoPlayback];
849
            [self.fspanel setActive];
850
        } else {
851
            [[self.controlsBar bottomBarView] setHidden: NO];
852
            [self.fspanel setNonActive];
853
        }
854
    }
855
}
856

857 858 859 860 861
#pragma mark -
#pragma mark Lion native fullscreen handling
- (void)windowWillEnterFullScreen:(NSNotification *)notification
{
    [super windowWillEnterFullScreen:notification];
862

863
    // update split view frame after removing title bar
864
    if (self.darkInterface) {
865
        NSRect frame = [[self contentView] frame];
866 867
        frame.origin.y += [self.controlsBar height];
        frame.size.height -= [self.controlsBar height];
868
        [_splitView setFrame:frame];
869
    }
870 871 872 873 874 875 876
}

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

    // update split view frame after readding title bar
877
    if (self.darkInterface) {
878
        NSRect frame = [_splitView frame];
879
        frame.size.height -= [self.titlebarView frame].size.height;
880
        [_splitView setFrame:frame];
881
    }
882
}
883 884
#pragma mark -
#pragma mark Fullscreen support
885

886 887
- (void)showFullscreenController
{
888 889
    id currentWindow = [NSApp keyWindow];
    if ([currentWindow respondsToSelector:@selector(hasActiveVideo)] && [currentWindow hasActiveVideo]) {
890
        if ([currentWindow respondsToSelector:@selector(fullscreen)] && [currentWindow fullscreen] && ![[currentWindow videoView] isHidden]) {
891 892

            if ([[VLCMain sharedInstance] activeVideoPlayback])
893
                [self.fspanel fadeIn];
894 895
        }
    }
896

897 898
}

899
#pragma mark -
900 901 902 903
#pragma mark split view delegate
- (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMax ofSubviewAt:(NSInteger)dividerIndex
{
    if (dividerIndex == 0)
904
        return 300.;
905 906 907 908
    else
        return proposedMax;
}

909 910 911 912 913 914 915 916
- (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMin ofSubviewAt:(NSInteger)dividerIndex
{
    if (dividerIndex == 0)
        return 100.;
    else
        return proposedMin;
}

917 918
- (BOOL)splitView:(NSSplitView *)splitView canCollapseSubview:(NSView *)subview
{
919
    return ([subview isEqual:_splitViewLeft]);
920 921
}

922 923
- (BOOL)splitView:(NSSplitView *)splitView shouldAdjustSizeOfSubview:(NSView *)subview
{
924
    return (![subview isEqual:_splitViewLeft]);
925 926
}

927
- (void)mainSplitViewDidResizeSubviews:(id)object
928
{
929
    f_lastLeftSplitViewWidth = [_splitViewLeft frame].size.width;
930
    config_PutInt(getIntf(), "macosx-show-sidebar", ![_splitView isSubviewCollapsed:_splitViewLeft]);
931 932 933 934 935
    [[[VLCMain sharedInstance] mainMenu] updateSidebarMenuItem];
}

- (void)toggleLeftSubSplitView
{
936 937 938
    [_splitView adjustSubviews];
    if ([_splitView isSubviewCollapsed:_splitViewLeft])
        [_splitView setPosition:f_lastLeftSplitViewWidth ofDividerAtIndex:0];
939
    else
940
        [_splitView setPosition:[_splitView minPossiblePositionOfDividerAtIndex:0] ofDividerAtIndex:0];
941
    [[[VLCMain sharedInstance] mainMenu] updateSidebarMenuItem];
942 943
}

944 945 946 947
#pragma mark -
#pragma mark private playlist magic
- (void)_updatePlaylistTitle
{
948
    PLRootType root = [[[[VLCMain sharedInstance] playlist] model] currentRootType];
949
    playlist_t *p_playlist = pl_Get(getIntf());
950

951 952
    PL_LOCK;
    if (root == ROOT_TYPE_PLAYLIST)
953
        [_categoryLabel setStringValue: [_NS("Playlist") stringByAppendingString:[self _playbackDurationOfNode:p_playlist->p_playing]]];
954
    else if (root == ROOT_TYPE_MEDIALIBRARY)
955
        [_categoryLabel setStringValue: [_NS("Media Library") stringByAppendingString:[self _playbackDurationOfNode:p_playlist->p_media_library]]];
956 957

    PL_UNLOCK;
958 959 960 961 962 963 964
}

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

965
    playlist_t * p_playlist = pl_Get(getIntf());
966 967
    PL_ASSERT_LOCKED;

968 969 970 971 972 973 974 975 976
    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];
977 978 979 980 981
    if (mt_duration >= 86400) {
        [formatter setDateFormat:@"dd:HH:mm:ss"];
    } else {
        [formatter setDateFormat:@"HH:mm:ss"];
    }
982 983
    [formatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];

984
    return [NSString stringWithFormat:@" — %@",[formatter stringFromDate:date]];
985 986
}

987 988
- (IBAction)searchItem:(id)sender
{
989
    [[[[VLCMain sharedInstance] playlist] model] searchUpdate:[_searchField stringValue]];
990 991
}

992 993 994 995 996
- (IBAction)highlightSearchField:(id)sender
{
    [_searchField selectText:sender];
}

997
#pragma mark -
998 999 1000 1001
#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
{
1002
    //Works the same way as the NSOutlineView data source: `nil` means a parent item
1003
    if (item==nil)
1004 1005 1006
        return [o_sidebaritems count];
    else
        return [[item children] count];
1007 1008 1009 1010