VLCMainWindow.m 51.6 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
        [_fspanel center];
248

Felix Paul Kühne's avatar
Felix Paul Kühne committed
249
        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.")];
250
        NSInteger returnValue = [albumArtAlert runModal];
251
        config_PutInt(getIntf(), "metadata-network-access", returnValue == NSAlertDefaultReturn);
252 253
    }

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

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

264
        NSRect winrect = [self frame];
265
        CGFloat f_titleBarHeight = [self.titlebarView frame].size.height;
266

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

501 502

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

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

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

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

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

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

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

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

            if (b_activeVideo)
                b_restored = YES;
549 550
        }

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

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

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

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

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

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

585

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

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

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

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

609 610
#pragma mark -
#pragma mark Update interface and respond to foreign events
611 612
- (void)showDropZone
{
613
    b_dropzone_active = YES;
614 615 616
    [_splitViewRight addSubview:_dropzoneView positioned:NSWindowAbove relativeTo:_playlistScrollView];
    [_dropzoneView setFrame:_playlistScrollView.frame];
    [_playlistScrollView setHidden:YES];
617 618 619 620
}

- (void)hideDropZone
{
621
    b_dropzone_active = NO;
622 623
    [_dropzoneView removeFromSuperview];
    [_playlistScrollView setHidden:NO];
624 625
}

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

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

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

647 648 649
    b_splitview_removed = YES;
}

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

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

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

    b_splitview_removed = NO;
}

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

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

    [[VLCCoreInteraction sharedInstance] updateAtoB];
684 685
}

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

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

704
        char *uri = input_item_GetURI(input_GetItem(p_input));
705

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

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

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

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

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

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

753 754
    bool b_seekable = false;

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

761
        vlc_object_release(p_input);
762 763
    }

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

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

    [self _updatePlaylistTitle];
778 779 780 781
}

- (void)setPause
{
782 783
    [self.controlsBar setPause];
    [self.fspanel setPause];
784

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

- (void)setPlay
{
792 793
    [self.controlsBar setPlay];
    [self.fspanel setPlay];
794

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

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

806 807
#pragma mark -
#pragma mark Video Output handling
808

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

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

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

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

831
            }
832 833 834
        }

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

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

841
        [self makeFirstResponder: _playlistScrollView];
842
        [[[VLCMain sharedInstance] voutController] updateWindowLevelForHelperWindows: NSNormalWindowLevel];
843

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

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

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

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

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

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

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

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

899 900
}

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

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

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

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

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

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

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

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

    PL_UNLOCK;
960 961 962 963 964 965 966
}

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

967
    playlist_t * p_playlist = pl_Get(getIntf());
968 969
    PL_ASSERT_LOCKED;

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

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

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

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

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


- (id)sourceList:(PXSourceList*)aSourceList child:(NSUInteger)index ofItem:(id)item
{
    //Works the same way as the NSOutlineView data source: `nil` means a parent item
1015
    if (item==nil)
1016
        return [o_sidebaritems objectAtIndex:index];
1017
    else
1018
        return [[item children] objectAtIndex:index];
1019 1020 1021 1022 1023
}


- (id)sourceList:(PXSourceList*)aSourceList objectValueForItem:(id)item
{
1024