VLCMainWindow.m 44.8 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
    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;

    NSRect frameBeforePlayback;
}
76 77
- (void)makeSplitViewVisible;
- (void)makeSplitViewHidden;
78 79
- (void)showPodcastControls;
- (void)hidePodcastControls;
80 81
@end

82
static const float f_min_window_height = 307.;
83

84 85
@implementation VLCMainWindow

86 87 88
#pragma mark -
#pragma mark Initialization

89 90 91 92 93
- (BOOL)isEvent:(NSEvent *)o_event forKey:(const char *)keyString
{
    char *key;
    NSString *o_key;

94
    key = config_GetPsz(keyString);
95
    o_key = [NSString stringWithFormat:@"%s", key];
96
    FREENULL(key);
97

98
    unsigned int i_keyModifiers = [[VLCStringUtility sharedInstance] VLCModifiersToCocoa:o_key];
99

100 101
    NSString * characters = [o_event charactersIgnoringModifiers];
    if ([characters length] > 0) {
102
        return [[characters lowercaseString] isEqualToString: [[VLCStringUtility sharedInstance] VLCKeyToString: o_key]] &&
103 104 105 106 107 108
                (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;
109 110
}

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

130 131 132
    VLCCoreInteraction *coreInteraction = [VLCCoreInteraction sharedInstance];
    return [coreInteraction hasDefinedShortcutKey:o_event force:b_force] ||
           [coreInteraction keyEvent:o_event];
133 134 135 136
}

- (void)dealloc
{
137
    [[NSNotificationCenter defaultCenter] removeObserver: self];
138 139 140 141
}

- (void)awakeFromNib
{
142 143
    [super awakeFromNib];

144 145 146 147 148 149 150 151
    /*
     * General setup
     */

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

    BOOL splitViewShouldBeHidden = NO;
152

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

163
    _nativeFullscreenMode = var_InheritBool(getIntf(), "macosx-nativefullscreenmode");
164
    b_dropzone_active = YES;
165

166
    // Playlist setup
167
    VLCPlaylist *playlist = [[VLCMain sharedInstance] playlist];
168 169
    [playlist setOutlineView:(VLCPlaylistView *)_outlineView];
    [playlist setPlaylistHeaderView:_outlineView.headerView];
170
    [self setNextResponder:playlist];
171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186

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


    /*
     * 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")];
187
    [_searchField.cell accessibilitySetOverrideValue:_NS("Search the playlist. Results will be selected in the table.")
188 189 190 191 192 193
                                        forAttribute:NSAccessibilityDescriptionAttribute];

    // Dropzone
    [_dropzoneLabel setStringValue:_NS("Drop media here")];
    [_dropzoneImageView setImage:imageFromRes(@"dropzone")];
    [_dropzoneButton setTitle:_NS("Open media...")];
194
    [_dropzoneButton.cell accessibilitySetOverrideValue:_NS("Open a dialog to select the media to play")
195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211
                                           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")];
212

213
    /* interface builder action */
214
    CGFloat f_threshold_height = f_min_video_height + [self.controlsBar height];
215
    if (self.darkInterface)
216
        f_threshold_height += [self.titlebarView frame].size.height;
217
    if ([[self contentView] frame].size.height < f_threshold_height)
218
        splitViewShouldBeHidden = YES;
219

220
    // Set that here as IB seems to be buggy
221
    if (self.darkInterface)
222
        [self setContentMinSize:NSMakeSize(604., f_min_window_height + [self.titlebarView frame].size.height)];
223
    else
224
        [self setContentMinSize:NSMakeSize(604., f_min_window_height)];
225

226 227
    _fspanel = [[VLCFSPanelController alloc] init];
    [_fspanel showWindow:self];
228

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

233
        [_sidebarView expandItem:nil expandChildren:YES];
234

Felix Paul Kühne's avatar
Felix Paul Kühne committed
235
        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.")];
236
        NSInteger returnValue = [albumArtAlert runModal];
237
        config_PutInt("metadata-network-access", returnValue == NSAlertDefaultReturn);
238 239
    }

240
    if (self.darkInterface) {
Felix Paul Kühne's avatar
Felix Paul Kühne committed
241 242
        [defaultCenter addObserver: self selector: @selector(windowResizedOrMoved:) name: NSWindowDidResizeNotification object: nil];
        [defaultCenter addObserver: self selector: @selector(windowResizedOrMoved:) name: NSWindowDidMoveNotification object: nil];
243

244 245
        [self setBackgroundColor: [NSColor clearColor]];
        [self setOpaque: NO];
246 247
        [self display];
        [self setHasShadow:NO];
248
        [self setHasShadow:YES];
249

250
        self.previousSavedFrame = [self frame];
251
    } else {
252 253
        [_playlistScrollView setBorderType:NSNoBorder];
        [_sidebarScrollView setBorderType:NSNoBorder];
254
    }
255

Felix Paul Kühne's avatar
Felix Paul Kühne committed
256 257 258
    [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];
259
    [defaultCenter addObserver: self selector: @selector(mainSplitViewDidResizeSubviews:) name: NSSplitViewDidResizeSubviewsNotification object:_splitView];
260

261 262
    if (splitViewShouldBeHidden) {
        [self hideSplitView:YES];
263
        f_lastSplitViewHeight = 300;
264
    }
265 266

    /* sanity check for the window size */
267
    NSRect frame = [self frame];
268 269
    NSSize screenSize = [[self screen] frame].size;
    if (screenSize.width <= frame.size.width || screenSize.height <= frame.size.height) {
270
        self.nativeVideoSize = screenSize;
271 272
        [self resizeWindow];
    }
273

274
    /* update fs button to reflect state for next startup */
275
    if (var_InheritBool(pl_Get(getIntf()), "fullscreen"))
276
        [self.controlsBar setFullscreenState:YES];
277 278

    /* restore split view */
279
    f_lastLeftSplitViewWidth = 200;
280
    [[[VLCMain sharedInstance] mainMenu] updateSidebarMenuItem: ![_splitView isSubviewCollapsed:_splitViewLeft]];
281 282
}

283
#pragma mark -
284
#pragma mark appearance management
285

286 287 288
- (void)reloadSidebar
{
    BOOL isAReload = NO;
289
    if (o_sidebaritems)
290 291 292 293 294
        isAReload = YES;

    o_sidebaritems = [[NSMutableArray alloc] init];
    SideBarItem *libraryItem = [SideBarItem itemWithTitle:_NS("LIBRARY") identifier:@"library"];
    SideBarItem *playlistItem = [SideBarItem itemWithTitle:_NS("Playlist") identifier:@"playlist"];
295
    [playlistItem setIcon: imageFromRes(@"sidebar-playlist")];
296
    SideBarItem *medialibraryItem = [SideBarItem itemWithTitle:_NS("Media Library") identifier:@"medialibrary"];
297
    [medialibraryItem setIcon: imageFromRes(@"sidebar-playlist")];
298 299 300 301 302
    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"];

303
    /* SD subnodes, inspired by the Qt intf */
304 305
    char **ppsz_longnames = NULL;
    int *p_categories = NULL;
306
    char **ppsz_names = vlc_sd_GetNames(pl_Get(getIntf()), &ppsz_longnames, &p_categories);
307
    if (!ppsz_names)
308
        msg_Err(getIntf(), "no sd item found"); //TODO
309 310 311 312 313 314 315 316
    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++) {
317
        o_identifier = toNSStr(*ppsz_name);
318 319 320
        switch (*p_category) {
            case SD_CAT_INTERNET:
                [internetItems addObject: [SideBarItem itemWithTitle: _NS(*ppsz_longname) identifier: o_identifier]];
321
                [[internetItems lastObject] setIcon: imageFromRes(@"sidebar-podcast")];
322 323 324 325
                [[internetItems lastObject] setSdtype: SD_CAT_INTERNET];
                break;
            case SD_CAT_DEVICES:
                [devicesItems addObject: [SideBarItem itemWithTitle: _NS(*ppsz_longname) identifier: o_identifier]];
326
                [[devicesItems lastObject] setIcon: imageFromRes(@"sidebar-local")];
327 328 329 330
                [[devicesItems lastObject] setSdtype: SD_CAT_DEVICES];
                break;
            case SD_CAT_LAN:
                [lanItems addObject: [SideBarItem itemWithTitle: _NS(*ppsz_longname) identifier: o_identifier]];
331
                [[lanItems lastObject] setIcon: imageFromRes(@"sidebar-local")];
332 333 334 335 336
                [[lanItems lastObject] setSdtype: SD_CAT_LAN];
                break;
            case SD_CAT_MYCOMPUTER:
                [mycompItems addObject: [SideBarItem itemWithTitle: _NS(*ppsz_longname) identifier: o_identifier]];
                if (!strncmp(*ppsz_name, "video_dir", 9))
337
                    [[mycompItems lastObject] setIcon: imageFromRes(@"sidebar-movie")];
338
                else if (!strncmp(*ppsz_name, "audio_dir", 9))
339
                    [[mycompItems lastObject] setIcon: imageFromRes(@"sidebar-music")];
340
                else if (!strncmp(*ppsz_name, "picture_dir", 11))
341
                    [[mycompItems lastObject] setIcon: imageFromRes(@"sidebar-pictures")];
342 343 344 345 346
                else
                    [[mycompItems lastObject] setIcon: [NSImage imageNamed:@"NSApplicationIcon"]];
                [[mycompItems lastObject] setSdtype: SD_CAT_MYCOMPUTER];
                break;
            default:
347
                msg_Warn(getIntf(), "unknown SD type found, skipping (%s)", *ppsz_name);
348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372
                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];

373 374 375
    [_sidebarView reloadData];
    [_sidebarView setDropItem:playlistItem dropChildIndex:NSOutlineViewDropOnItemIndex];
    [_sidebarView registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, @"VLCPlaylistItemPboardType", nil]];
376

377 378 379 380
    [_sidebarView setAutosaveName:@"mainwindow-sidebar"];
    [_sidebarView setDataSource:self];
    [_sidebarView setDelegate:self];
    [_sidebarView setAutosaveExpandedItems:YES];
381

382
    [_sidebarView expandItem:libraryItem expandChildren:YES];
383 384

    if (isAReload) {
385
        [_sidebarView expandItem:nil expandChildren:YES];
386 387 388
    }
}

389
// Show split view and hide the video view
390 391
- (void)makeSplitViewVisible
{
392
    if (self.darkInterface)
393
        [self setContentMinSize: NSMakeSize(604., f_min_window_height + [self.titlebarView frame].size.height)];
394
    else
395
        [self setContentMinSize: NSMakeSize(604., f_min_window_height)];
396 397

    NSRect old_frame = [self frame];
398
    CGFloat newHeight = [self minSize].height;
399
    if (old_frame.size.height < newHeight) {
400 401 402 403
        NSRect new_frame = old_frame;
        new_frame.origin.y = old_frame.origin.y + old_frame.size.height - newHeight;
        new_frame.size.height = newHeight;

404
        [[self animator] setFrame:new_frame display:YES animate:YES];
405 406
    }

407 408
    [self.videoView setHidden:YES];
    [_splitView setHidden:NO];
409
    if (self.nativeFullscreenMode && [self fullscreen]) {
410
        [self showControlsBar];
411
        [self.fspanel setNonActive];
412 413
    }

414
    [self makeFirstResponder:_playlistScrollView];
415 416
}

417
// Hides the split view and makes the vout view in foreground
418 419
- (void)makeSplitViewHidden
{
420
    if (self.darkInterface)
421
        [self setContentMinSize: NSMakeSize(604., f_min_video_height + [self.titlebarView frame].size.height)];
422
    else
423
        [self setContentMinSize: NSMakeSize(604., f_min_video_height)];
424

425 426
    [_splitView setHidden:YES];
    [self.videoView setHidden:NO];
427
    if (self.nativeFullscreenMode && [self fullscreen]) {
428
        [self hideControlsBar];
429
        [self.fspanel setActive];
430
    }
431

432
    if ([[self.videoView subviews] count] > 0)
433
        [self makeFirstResponder: [[self.videoView subviews] firstObject]];
434 435
}

436 437

- (void)changePlaylistState:(VLCPlaylistStateEvent)event
438
{
439 440
    // Beware, this code is really ugly

441
    msg_Dbg(getIntf(), "toggle playlist from state: removed splitview %i, minimized view %i. Event %i", b_splitview_removed, b_minimized_view, event);
442 443
    if (![self isVisible] && event == psUserMenuEvent) {
        [self makeKeyAndOrderFront: nil];
444
        return;
445 446
    }

447
    BOOL b_activeVideo = [[VLCMain sharedInstance] activeVideoPlayback];
448
    BOOL b_restored = NO;
449

450
    // ignore alt if triggered through main menu shortcut
451
    BOOL b_have_alt_key = ([[NSApp currentEvent] modifierFlags] & NSAlternateKeyMask) != 0;
452
    if (event == psUserMenuEvent)
453 454
        b_have_alt_key = NO;

455 456 457 458
    // eUserMenuEvent is now handled same as eUserEvent
    if(event == psUserMenuEvent)
        event = psUserEvent;

459
    if (b_dropzone_active && b_have_alt_key) {
460 461 462 463
        [self hideDropZone];
        return;
    }

464 465
    if (!(self.nativeFullscreenMode && self.fullscreen) && !b_splitview_removed && ((b_have_alt_key && b_activeVideo)
                                                                              || (self.nonembedded && event == psUserEvent)
466 467
                                                                              || (!b_activeVideo && event == psUserEvent)
                                                                              || (b_minimized_view && event == psVideoStartedOrStoppedEvent))) {
468 469
        // for starting playback, window is resized through resized events
        // for stopping playback, resize through reset to previous frame
470
        [self hideSplitView: event != psVideoStartedOrStoppedEvent];
471 472
        b_minimized_view = NO;
    } else {
473
        if (b_splitview_removed) {
474
            if (!self.nonembedded || (event == psUserEvent && self.nonembedded))
475
                [self showSplitView: event != psVideoStartedOrStoppedEvent];
476

477
            if (event != psUserEvent)
478 479 480 481 482 483
                b_minimized_view = YES;
            else
                b_minimized_view = NO;

            if (b_activeVideo)
                b_restored = YES;
484 485
        }

486 487
        if (!self.nonembedded) {
            if (([self.videoView isHidden] && b_activeVideo) || b_restored || (b_activeVideo && event != psUserEvent))
488
                [self makeSplitViewHidden];
489
            else
490
                [self makeSplitViewVisible];
491
        } else {
492 493
            [_splitView setHidden: NO];
            [_playlistScrollView setHidden: NO];
494
            [self.videoView setHidden: YES];
495
            [self showControlsBar];
496
        }
497
    }
498

499
    msg_Dbg(getIntf(), "toggle playlist to state: removed splitview %i, minimized view %i", b_splitview_removed, b_minimized_view);
500 501
}

502 503 504 505 506
- (IBAction)dropzoneButtonAction:(id)sender
{
    [[[VLCMain sharedInstance] open] openFileGeneric];
}

507 508 509
#pragma mark -
#pragma mark overwritten default functionality

510 511
- (void)windowResizedOrMoved:(NSNotification *)notification
{
512
    [self saveFrameUsingName:[self frameAutosaveName]];
513 514
}

515 516
- (void)applicationWillTerminate:(NSNotification *)notification
{
517
    [self saveFrameUsingName:[self frameAutosaveName]];
518 519
}

520

521 522
- (void)someWindowWillClose:(NSNotification *)notification
{
523
    id obj = [notification object];
524

525 526
    // hasActiveVideo is defined for VLCVideoWindowCommon and subclasses
    if ([obj respondsToSelector:@selector(hasActiveVideo)] && [obj hasActiveVideo]) {
527 528 529
        if ([[VLCMain sharedInstance] activeVideoPlayback])
            [[VLCCoreInteraction sharedInstance] stop];
    }
530 531 532 533
}

- (void)someWindowWillMiniaturize:(NSNotification *)notification
{
534
    if (config_GetInt("macosx-pause-minimized")) {
535
        id obj = [notification object];
536

537
        if ([obj class] == [VLCVideoWindowCommon class] || [obj class] == [VLCDetachedVideoWindow class] || ([obj class] == [VLCMainWindow class] && !self.nonembedded)) {
538
            if ([[VLCMain sharedInstance] activeVideoPlayback])
539 540
                [[VLCCoreInteraction sharedInstance] pause];
        }
541 542 543
    }
}

544 545
#pragma mark -
#pragma mark Update interface and respond to foreign events
546 547
- (void)showDropZone
{
548
    b_dropzone_active = YES;
549
    [_dropzoneView setHidden:NO];
550
    [_playlistScrollView setHidden:YES];
551 552 553 554
}

- (void)hideDropZone
{
555
    b_dropzone_active = NO;
556
    [_dropzoneView setHidden:YES];
557
    [_playlistScrollView setHidden:NO];
558 559
}

560
- (void)hideSplitView:(BOOL)resize
561
{
562
    if (resize) {
563
        NSRect winrect = [self frame];
564
        f_lastSplitViewHeight = [_splitView frame].size.height;
565 566
        winrect.size.height = winrect.size.height - f_lastSplitViewHeight;
        winrect.origin.y = winrect.origin.y + f_lastSplitViewHeight;
567
        [self setFrame:winrect display:YES animate:YES];
568 569
    }

570
    if (self.darkInterface) {
571 572
        [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)];
573
    } else {
574 575
        [self setContentMinSize: NSMakeSize(604., [self.controlsBar height])];
        [self setContentMaxSize: NSMakeSize(FLT_MAX, [self.controlsBar height])];
576
    }
577

578 579 580
    b_splitview_removed = YES;
}

581
- (void)showSplitView:(BOOL)resize
582
{
583
    [self updateWindow];
584
    if (self.darkInterface)
585
        [self setContentMinSize:NSMakeSize(604., f_min_window_height + [self.titlebarView frame].size.height)];
586
    else
587
        [self setContentMinSize:NSMakeSize(604., f_min_window_height)];
588
    [self setContentMaxSize: NSMakeSize(FLT_MAX, FLT_MAX)];
589

590
    if (resize) {
591 592
        NSRect winrect;
        winrect = [self frame];
593 594
        winrect.size.height = winrect.size.height + f_lastSplitViewHeight;
        winrect.origin.y = winrect.origin.y - f_lastSplitViewHeight;
595
        [self setFrame:winrect display:YES animate:YES];
596
    }
597 598 599 600

    b_splitview_removed = NO;
}

601 602
- (void)updateTimeSlider
{
603 604
    [self.controlsBar updateTimeSlider];
    [self.fspanel updatePositionAndTime];
605

606 607 608
    [[[VLCMain sharedInstance] voutController] updateControlsBarsUsingBlock:^(VLCControlsBarCommon *controlsBar) {
        [controlsBar updateTimeSlider];
    }];
609 610

    [[VLCCoreInteraction sharedInstance] updateAtoB];
611 612
}

613
- (void)updateName
614
{
615
    input_thread_t *p_input;
616
    p_input = pl_CurrentInput(getIntf());
617
    if (p_input) {
618
        NSString *aString = @"";
619

620
        if (!config_GetPsz("video-title")) {
621
            char *format = var_InheritString(getIntf(), "input-title-format");
622
            if (format) {
623
                char *formated = vlc_strfinput(p_input, format);
624 625 626 627
                free(format);
                aString = toNSStr(formated);
                free(formated);
            }
628
        } else
629
            aString = toNSStr(config_GetPsz("video-title"));
630

631
        char *uri = input_item_GetURI(input_GetItem(p_input));
632

633
        NSURL * o_url = [NSURL URLWithString:toNSStr(uri)];
634
        if ([o_url isFileURL]) {
635
            [self setRepresentedURL: o_url];
636 637 638
            [[[VLCMain sharedInstance] voutController] updateWindowsUsingBlock:^(VLCVideoWindowCommon *o_window) {
                [o_window setRepresentedURL:o_url];
            }];
639
        } else {
640
            [self setRepresentedURL: nil];
641 642 643
            [[[VLCMain sharedInstance] voutController] updateWindowsUsingBlock:^(VLCVideoWindowCommon *o_window) {
                [o_window setRepresentedURL:nil];
            }];
644
        }
645
        free(uri);
646

647
        if ([aString isEqualToString:@""]) {
648 649 650 651
            if ([o_url isFileURL])
                aString = [[NSFileManager defaultManager] displayNameAtPath: [o_url path]];
            else
                aString = [o_url absoluteString];
652
        }
653

654 655 656 657 658 659
        if ([aString length] > 0) {
            [self setTitle: aString];
            [[[VLCMain sharedInstance] voutController] updateWindowsUsingBlock:^(VLCVideoWindowCommon *o_window) {
                [o_window setTitle:aString];
            }];

660
            [self.fspanel setStreamTitle: aString];
661
        } else {
662
            [self setTitle: _NS("VLC media player")];
663 664
            [self setRepresentedURL: nil];
        }
665

666 667
        vlc_object_release(p_input);
    } else {
668
        [self setTitle: _NS("VLC media player")];
669
        [self setRepresentedURL: nil];
670
    }
671 672
}

673 674
- (void)updateWindow
{
675
    [self.controlsBar updateControls];
676 677 678
    [[[VLCMain sharedInstance] voutController] updateControlsBarsUsingBlock:^(VLCControlsBarCommon *controlsBar) {
        [controlsBar updateControls];
    }];
679

680 681
    bool b_seekable = false;

682
    playlist_t *p_playlist = pl_Get(getIntf());
683
    input_thread_t *p_input = playlist_CurrentInput(p_playlist);
684
    if (p_input) {
685
        /* seekable streams */
686
        b_seekable = var_GetBool(p_input, "can-seek");
687

688
        vlc_object_release(p_input);
689 690
    }

691
    [self updateTimeSlider];
692 693
    if ([self.fspanel respondsToSelector:@selector(setSeekable:)])
        [self.fspanel setSeekable: b_seekable];
694 695

    PL_LOCK;
696 697
    if ([[[[VLCMain sharedInstance] playlist] model] currentRootType] != ROOT_TYPE_PLAYLIST ||
        [[[[VLCMain sharedInstance] playlist] model] hasChildren])
698 699 700 701
        [self hideDropZone];
    else
        [self showDropZone];
    PL_UNLOCK;
702
    [_sidebarView setNeedsDisplay:YES];
703 704

    [self _updatePlaylistTitle];
705 706 707 708
}

- (void)setPause
{
709 710
    [self.controlsBar setPause];
    [self.fspanel setPause];
711

712 713 714
    [[[VLCMain sharedInstance] voutController] updateControlsBarsUsingBlock:^(VLCControlsBarCommon *controlsBar) {
        [controlsBar setPause];
    }];
715 716 717 718
}

- (void)setPlay
{
719 720
    [self.controlsBar setPlay];
    [self.fspanel setPlay];
721

722 723 724
    [[[VLCMain sharedInstance] voutController] updateControlsBarsUsingBlock:^(VLCControlsBarCommon *controlsBar) {
        [controlsBar setPlay];
    }];
725 726
}

727
- (void)updateVolumeSlider
728
{
729
    [(VLCMainWindowControlsBar *)[self controlsBar] updateVolumeSlider];
730
    [self.fspanel setVolumeLevel:[[VLCCoreInteraction sharedInstance] volume]];
731 732
}

733 734
#pragma mark -
#pragma mark Video Output handling
735

736 737
- (void)videoplayWillBeStarted
{
738
    if (!self.fullscreen)
739 740 741
        frameBeforePlayback = [self frame];
}

742
- (void)setVideoplayEnabled
743
{
744
    BOOL b_videoPlayback = [[VLCMain sharedInstance] activeVideoPlayback];
745

746
    if (!b_videoPlayback) {
747
        if (!self.nonembedded && (!self.nativeFullscreenMode || (self.nativeFullscreenMode && !self.fullscreen)) && frameBeforePlayback.size.width > 0 && frameBeforePlayback.size.height > 0) {
748 749

            // only resize back to minimum view of this is still desired final state
750
            CGFloat f_threshold_height = f_min_video_height + [self.controlsBar height];
751
            if(frameBeforePlayback.size.height > f_threshold_height || b_minimized_view) {
752 753 754 755 756 757

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

758
            }
759 760 761
        }

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

763
        // update fs button to reflect state for next startup
764
        if (var_InheritBool(getIntf(), "fullscreen") || var_GetBool(pl_Get(getIntf()), "fullscreen")) {
765
            [self.controlsBar setFullscreenState:YES];
766 767
        }

768
        [self makeFirstResponder: _playlistScrollView];
769
        [[[VLCMain sharedInstance] voutController] updateWindowLevelForHelperWindows: NSNormalWindowLevel];
770

771 772 773
        // restore alpha value to 1 for the case that macosx-opaqueness is set to < 1
        [self setAlphaValue:1.0];
    }
774

775
    if (self.nativeFullscreenMode) {
776 777
        if ([self hasActiveVideo] && [self fullscreen] && b_videoPlayback) {
            [self hideControlsBar];
778
            [self.fspanel setActive];
779
        } else {
780
            [self showControlsBar];
781
            [self.fspanel setNonActive];
782
        }
783
    }
784
}
785

786 787
#pragma mark -
#pragma mark Fullscreen support
788

789 790
- (void)showFullscreenController
{
791 792
    id currentWindow = [NSApp keyWindow];
    if ([currentWindow respondsToSelector:@selector(hasActiveVideo)] && [currentWindow hasActiveVideo]) {
793
        if ([currentWindow respondsToSelector:@selector(fullscreen)] && [currentWindow fullscreen] && ![[currentWindow videoView] isHidden]) {
794 795

            if ([[VLCMain sharedInstance] activeVideoPlayback])
796
                [self.fspanel fadeIn];
797 798
        }
    }
799

800 801
}

802
#pragma mark -
803 804 805 806
#pragma mark split view delegate
- (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMax ofSubviewAt:(NSInteger)dividerIndex
{
    if (dividerIndex == 0)
807
        return 300.;
808 809 810 811
    else
        return proposedMax;
}

812 813 814 815 816 817 818 819
- (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMin ofSubviewAt:(NSInteger)dividerIndex
{
    if (dividerIndex == 0)
        return 100.;
    else
        return proposedMin;
}

820 821
- (BOOL)splitView:(NSSplitView *)splitView canCollapseSubview:(NSView *)subview
{
822
    return ([subview isEqual:_splitViewLeft]);
823 824
}

825 826
- (BOOL)splitView:(NSSplitView *)splitView shouldAdjustSizeOfSubview:(NSView *)subview
{
827
    return (![subview isEqual:_splitViewLeft]);
828 829
}

830
- (void)mainSplitViewDidResizeSubviews:(id)object
831
{
832
    f_lastLeftSplitViewWidth = [_splitViewLeft frame].size.width;
833
    [[[VLCMain sharedInstance] mainMenu] updateSidebarMenuItem: ![_splitView isSubviewCollapsed:_splitViewLeft]];
834 835 836 837
}

- (void)toggleLeftSubSplitView
{
838 839 840
    [_splitView adjustSubviews];
    if ([_splitView isSubviewCollapsed:_splitViewLeft])
        [_splitView setPosition:f_lastLeftSplitViewWidth ofDividerAtIndex:0];
841
    else
842
        [_splitView setPosition:[_splitView minPossiblePositionOfDividerAtIndex:0] ofDividerAtIndex:0];
843 844

    [[[VLCMain sharedInstance] mainMenu] updateSidebarMenuItem: ![_splitView isSubviewCollapsed:_splitViewLeft]];
845 846
}

847 848 849 850
#pragma mark -
#pragma mark private playlist magic
- (void)_updatePlaylistTitle
{
851
    PLRootType root = [[[[VLCMain sharedInstance] playlist] model] currentRootType];
852
    playlist_t *p_playlist = pl_Get(getIntf());
853

854 855
    PL_LOCK;
    if (root == ROOT_TYPE_PLAYLIST)
856
        [_categoryLabel setStringValue: [_NS("Playlist") stringByAppendingString:[self _playbackDurationOfNode:p_playlist->p_playing]]];
857
    else if (root == ROOT_TYPE_MEDIALIBRARY)
858
        [_categoryLabel setStringValue: [_NS("Media Library") stringByAppendingString:[self _playbackDurationOfNode:p_playlist->p_media_library]]];
859 860

    PL_UNLOCK;
861 862 863 864 865 866 867
}

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

868
    playlist_t * p_playlist = pl_Get(getIntf());
869 870
    PL_ASSERT_LOCKED;

871 872 873 874 875 876 877
    mtime_t mt_duration = playlist_GetNodeDuration( node );

    if (mt_duration < 1)
        return @"";

    mt_duration = mt_duration / 1000000;

878 879 880 881
    NSDateComponentsFormatter *formatter = [[NSDateComponentsFormatter alloc] init];
    formatter.unitsStyle = NSDateComponentsFormatterUnitsStyleAbbreviated;

    NSString* outputString = [formatter stringFromTimeInterval:mt_duration];
882

883
    return [NSString stringWithFormat:@" — %@", outputString];
884 885
}

886 887
- (IBAction)searchItem:(id)sender
{
888
    [[[[VLCMain sharedInstance] playlist] model] searchUpdate:[_searchField stringValue]];
889 890
}

891 892 893 894 895
- (IBAction)highlightSearchField:(id)sender
{
    [_searchField selectText:sender];
}

896
#pragma mark -
897 898 899 900
#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
{
901
    //Works the same way as the NSOutlineView data source: `nil` means a parent item
902
    if (item==nil)
903 904 905
        return [o_sidebaritems count];
    else
        return [[item children] count];
906 907 908 909 910 911
}


- (id)sourceList:(PXSourceList*)aSourceList child:(NSUInteger)index ofItem:(id)item
{
    //Works the same way as the NSOutlineView data source: `nil` means a parent item
912
    if (item==nil)
913
        return [o_sidebaritems objectAtIndex:index];
914
    else
915
        return [[item children] objectAtIndex:index];
916 917 918 919 920
}


- (id)sourceList:(PXSourceList*)aSourceList objectValueForItem:(id)item
{
921
    return [item title];
922 923 924 925
}

- (void)sourceList:(PXSourceList*)aSourceList setObjectValue:(id)object forItem:(id)item
{
926
    [item setTitle:object];
927 928 929 930
}

- (BOOL)sourceList:(PXSourceList*)aSourceList isItemExpandable:(id)item
{
931
    return [item hasChildren];
932 933 934 935 936
}


- (BOOL)sourceList:(PXSourceList*)aSourceList itemHasBadge:(id)item
{
937
    if ([[item identifier] isEqualToString: @"playlist"] || [[item identifier] isEqualToString: @"medialibrary"])
938 939
        return YES;

940
    return [item hasBadge];
941 942 943 944 945
}


- (NSInteger)sourceList:(PXSourceList*)aSourceList badgeValueForItem:(id)item
{
946
    playlist_t * p_playlist = pl_Get(getIntf());
947
    NSInteger i_playlist_size = 0;
948

949
    if ([[item identifier] isEqualToString: @"playlist"]) {
950
        PL_LOCK;
951
        i_playlist_size = p_playlist->p_playing->i_children;
952 953 954 955
        PL_UNLOCK;

        return i_playlist_size;
    }
956
    if ([[item identifier] isEqualToString: @"medialibrary"]) {
957
        PL_LOCK;
958 959
        if (p_playlist->p_media_library)
            i_playlist_size = p_playlist->p_media_library->i_children;
960 961 962 963
        PL_UNLOCK;

        return i_playlist_size;
    }
964 965

    return [item badgeValue];
966 967 968 969 970
}


- (BOOL)sourceList:(PXSourceList*)aSourceList itemHasIcon:(id)item
{
971
    return [item hasIcon];
972 973 974 975 976
}


- (NSImage*)sourceList:(PXSourceList*)aSourceList iconForItem:(id)item
{
977
    return [item icon];
978 979 980 981
}

- (NSMenu*)sourceList:(PXSourceList*)aSourceList menuForEvent:(NSEvent*)theEvent item:(id)item
{
982 983
    if ([theEvent type] == NSRightMouseDown || ([theEvent type] == NSLeftMouseDown && ([theEvent modifierFlags] & NSControlKeyMask) == NSControlKeyMask)) {
        if (item != nil) {
984 985
            if ([item sdtype] > 0)
            {
986
                NSMenu *m = [[NSMenu alloc] init];
987
                playlist_t * p_playlist = pl_Get(getIntf());
988
                BOOL sd_loaded = playlist_IsServicesDiscoveryLoaded(p_playlist, [[item identifier] UTF8String]);
989 990 991 992 993
                if (!sd_loaded)
                    [m addItemWithTitle:_NS("Enable") action:@selector(sdmenuhandler:) keyEquivalent:@""];
                else
                    [m addItemWithTitle:_NS("Disable") action:@selector(sdmenuhandler:) keyEquivalent:@""];
                [[m itemAtIndex:0] setRepresentedObject: [item identifier]];
994
                return m;
995 996
            }
        }
997 998 999
    }

    return nil;
1000 1001
}

1002 1003 1004
- (IBAction)sdmenuhandler:(id)sender
{
    NSString * identifier = [sender representedObject];
1005
    if ([identifier length] > 0 && ![identifier isEqualToString:@"lua{sd='freebox',longname='Freebox TV'}"]) {
1006
        playlist_t * p_playlist = pl_Get(getIntf());
1007
        BOOL sd_loaded = playlist_IsServicesDiscoveryLoaded(p_playlist, [identifier UTF8String]);
1008 1009

        if (!sd_loaded)
1010
            playlist_ServicesDiscoveryAdd(p_playlist, [identifier UTF8String]);
1011
        else
1012
            playlist_ServicesDiscoveryRemove(p_playlist, [identifier UTF8String]);
1013 1014 1015
    }
}

1016 1017 1018 1019 1020
#pragma mark -
#pragma mark Side Bar Delegate Methods
/* taken under BSD-new from the PXSourceList sample project, adapted for VLC */
- (BOOL)sourceList:(PXSourceList*)aSourceList isGroupAlwaysExpanded:(id)group
{
1021 1022 1023
    if ([[group identifier] isEqualToString:@"library"])
        return YES;

1024
    return NO;
1025 1026 1027 1028
}

- (void)sourceListSelectionDidChange:(NSNotification *)notification
{
1029
    playlist_t * p_playlist = pl_Get(getIntf());
1030

1031 1032
    NSIndexSet *selectedIndexes = [_sidebarView selectedRowIndexes];
    id item = [_sidebarView itemAtRow:[selectedIndexes firstIndex]];
1033