/*****************************************************************************
 * VLCOpenWindowController.m: Open dialogues for VLC's MacOS X port
 *****************************************************************************
 * Copyright (C) 2002-2021 VLC authors and VideoLAN
 *
 * Authors: Jon Lech Johansen <jon-vl@nanocrew.net>
 *          Christophe Massiot <massiot@via.ecp.fr>
 *          Derk-Jan Hartman <thedj@users.sourceforge.net>
 *          Benjamin Pracht <bigben at videolan dot org>
 *          Felix Paul Kühne <fkuehne at videolan dot org>
 *
 * 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.
 *****************************************************************************/

#import "VLCOpenWindowController.h"

#import <Cocoa/Cocoa.h>
#import <AVFoundation/AVFoundation.h>

#import <vlc_common.h>
#import <vlc_configuration.h>
#import <vlc_url.h>

#import "extensions/NSString+Helpers.h"
#import "extensions/VLCPositionFormatter.h"
#import "main/VLCMain.h"
#import "playlist/VLCPlaylistController.h"
#import "windows/convertandsave/VLCOutput.h"
#import "windows/VLCOpenInputMetadata.h"

NSString *const VLCOpenFileTabViewId = @"file";
NSString *const VLCOpenDiscTabViewId = @"disc";
NSString *const VLCOpenNetworkTabViewId = @"network";
NSString *const VLCOpenCaptureTabViewId = @"capture";
NSString *const VLCOpenTextFieldWasClicked = @"VLCOpenTextFieldWasClicked";

@interface VLCOpenBlockDeviceDescription : NSObject

@property (readwrite, retain) NSString *path;
@property (readwrite, retain) NSString *devicePath;
@property (readwrite, retain) NSString *mediaType;
@property (readwrite, retain) NSImage *mediaIcon;

@end

@implementation VLCOpenBlockDeviceDescription
@end

@interface VLCOpenDisplayInformation : NSObject

@property (readwrite) CGRect displayBounds;
@property (readwrite) CGDirectDisplayID displayID;

@end

@implementation VLCOpenDisplayInformation
@end

@interface VLCOpenTextField : NSTextField

@end

@implementation VLCOpenTextField

- (void)mouseDown:(NSEvent *)theEvent
{
    [NSNotificationCenter.defaultCenter postNotificationName: VLCOpenTextFieldWasClicked
                                                        object: self];
    [super mouseDown: theEvent];
}

@end

@interface VLCOpenWindowController()
{
    VLCOutput *_output;
    BOOL b_outputNibLoaded;
    NSArray *_avvideoDevices;
    NSArray *_avaudioDevices;
    NSString *_avCurrentDeviceUID;
    NSString *_avCurrentAudioDeviceUID;

    BOOL b_autoplay;
    BOOL b_nodvdmenus;
    NSView *_currentOpticalMediaView;
    NSImageView *_currentOpticalMediaIconView;
    NSMutableArray <VLCOpenBlockDeviceDescription *>*_allMediaDevices;
    NSArray <VLCOpenBlockDeviceDescription *>*_opticalDevices;
    NSMutableArray <VLCOpenBlockDeviceDescription *>*_specialMediaFolders;
    NSString *_filePath;
    NSString *_fileSlavePath;
    NSString *_subPath;
    NSString *_MRL;
    NSMutableArray *_displayInfos;
}

@property (readwrite, assign) NSString *MRL;

@end

@implementation VLCOpenWindowController

#pragma mark -
#pragma mark Init

- (id)init
{
    self = [super initWithWindowNibName:@"Open"];
    return self;
}

- (void)windowDidLoad
{
    _output = [VLCOutput new];

    [self.window setCollectionBehavior: NSWindowCollectionBehaviorFullScreenAuxiliary];

    [self initStrings];

    // setup start / stop time fields
    [_fileStartTimeTextField setFormatter:[[VLCPositionFormatter alloc] init]];
    [_fileStopTimeTextField setFormatter:[[VLCPositionFormatter alloc] init]];

    // Auto collapse MRL field
    self.mrlViewHeightConstraint.constant = 0;

    [self updateVideoDevicesAndRepresentation];

    [self updateAudioDevicesAndRepresentation];

    [self setupSubtitlesPanel];

    NSNotificationCenter *notificationCenter = NSNotificationCenter.defaultCenter;
    [notificationCenter addObserver: self
                           selector: @selector(openNetInfoChanged:)
                               name: NSControlTextDidChangeNotification
                             object: _netUDPPortTextField];
    [notificationCenter addObserver: self
                           selector: @selector(openNetInfoChanged:)
                               name: NSControlTextDidChangeNotification
                             object: _netUDPMAddressTextField];
    [notificationCenter addObserver: self
                           selector: @selector(openNetInfoChanged:)
                               name: NSControlTextDidChangeNotification
                             object: _netUDPMPortTextField];
    [notificationCenter addObserver: self
                           selector: @selector(openNetInfoChanged:)
                               name: NSControlTextDidChangeNotification
                             object: _netHTTPURLTextField];

    [notificationCenter addObserver: self
                           selector: @selector(screenFPSfieldChanged:)
                               name: NSControlTextDidChangeNotification
                             object: _screenFPSTextField];

    /* register clicks on text fields */
    [notificationCenter addObserver: self
                           selector: @selector(textFieldWasClicked:)
                               name: VLCOpenTextFieldWasClicked
                             object: nil];

    /* we want to be notified about removed or added media */
    _allMediaDevices = [[NSMutableArray alloc] init];
    _specialMediaFolders = [[NSMutableArray alloc] init];
    _displayInfos = [[NSMutableArray alloc] init];
    NSNotificationCenter *sharedNotificationCenter = NSWorkspace.sharedWorkspace.notificationCenter;
    [sharedNotificationCenter addObserver:self selector:@selector(scanOpticalMedia:) name:NSWorkspaceDidMountNotification object:nil];
    [sharedNotificationCenter addObserver:self selector:@selector(scanOpticalMedia:) name:NSWorkspaceDidUnmountNotification object:nil];

    [self qtkToggleUIElements:nil];
    [self updateMediaSelector:NO];
    [self scanOpticalMedia:nil];

    [self setMRL: @""];
}

- (void)initStrings
{
    [self.window setTitle: _NS("Open Source")];
    [_mrlButtonLabel setTitle: _NS("Media Resource Locator (MRL)")];

    [_okButton setTitle: _NS("Open")];
    [_cancelButton setTitle: _NS("Cancel")];

    [_outputCheckbox setTitle:_NS("Stream output:")];
    [_outputSettingsButton setTitle:_NS("Settings...")];

    _tabView.accessibilityLabel = _NS("Choose media input type");
    [[_tabView tabViewItemAtIndex: 0] setLabel: _NS("File")];
    [[_tabView tabViewItemAtIndex: 1] setLabel: _NS("Disc")];
    [[_tabView tabViewItemAtIndex: 2] setLabel: _NS("Network")];
    [[_tabView tabViewItemAtIndex: 3] setLabel: _NS("Capture")];
    [_fileNameLabel setStringValue: @""];
    [_fileNameStubLabel setStringValue: _NS("Choose a file")];
    [_fileIconWell setImage: [NSImage imageNamed:@"generic"]];
    [_fileBrowseButton setTitle: _NS("Browse...")];
    _fileBrowseButton.accessibilityLabel = _NS("Select a file for playback");
    [_fileTreatAsPipeButton setTitle: _NS("Treat as a pipe rather than as a file")];
    [_fileTreatAsPipeButton setHidden: NO];
    [_fileSlaveCheckbox setTitle: _NS("Play another media synchronously")];
    [_fileSelectSlaveButton setTitle: _NS("Choose...")];
    _fileBrowseButton.accessibilityLabel = _NS("Select another file to play in sync with the previously selected file");
    [_fileSlaveFilenameLabel setStringValue: @""];
    [_fileSlaveIconWell setImage: NULL];
    [_fileSubtitlesFilenameLabel setStringValue: @""];
    [_fileSubtitlesIconWell setImage: NULL];
    [_fileCustomTimingCheckbox setTitle: _NS("Custom playback")];
    [_fileStartTimeLabel setStringValue: _NS("Start time")];
    [_fileStartTimeTextField setStringValue: @""];
    [_fileStopTimeLabel setStringValue: _NS("Stop time")];
    [_fileStopTimeTextField setStringValue: @""];

    [_discSelectorPopup removeAllItems];
    [_discSelectorPopup setHidden: NO];
    NSString *oVideoTS = _NS("Open VIDEO_TS / BDMV folder");
    [_discNoDiscLabel setStringValue: _NS("Insert Disc")];
    [_discNoDiscVideoTSButton setTitle: oVideoTS];
    [_discAudioCDLabel setStringValue: _NS("Audio CD")];
    [_discAudioCDTrackCountLabel setStringValue: @""];
    [_discAudioCDVideoTSButton setTitle: oVideoTS];
    [_discDVDLabel setStringValue: @""];
    [_discDVDDisableMenusButton setTitle: _NS("Disable DVD menus")];
    [_discDVDVideoTSButton setTitle: oVideoTS];
    [_discDVDwomenusLabel setStringValue: @""];
    [_discDVDwomenusEnableMenusButton setTitle: _NS("Enable DVD menus")];
    [_discDVDwomenusVideoTSButton setTitle: oVideoTS];
    [_discDVDwomenusTitleLabel setStringValue: _NS("Title")];
    [_discDVDwomenusChapterLabel setStringValue: _NS("Chapter")];
    [_discVCDTitleLabel setStringValue: _NS("Title")];
    [_discVCDChapterLabel setStringValue: _NS("Chapter")];
    [_discVCDVideoTSButton setTitle: oVideoTS];
    [_discBDVideoTSButton setTitle: oVideoTS];

    [_netUDPPortLabel setStringValue: _NS("Port")];
    [_netUDPMAddressLabel setStringValue: _NS("IP Address")];
    [_netUDPMPortLabel setStringValue: _NS("Port")];
    [_netHTTPURLLabel setStringValue: _NS("URL")];
    [_netHelpLabel setStringValue: _NS("To Open a usual network stream (HTTP, RTSP, RTMP, MMS, FTP, etc.), just enter the URL in the field above. If you want to open a RTP or UDP stream, press the button below.")];
    [_netHelpUDPLabel setStringValue: _NS("If you want to open a multicast stream, enter the respective IP address given by the stream provider. In unicast mode, VLC will use your machine's IP automatically.\n\nTo open a stream using a different protocol, just press Cancel to close this sheet.")];
    _netHTTPURLTextField.accessibilityLabel = _NS("Enter a stream URL here. To open RTP or UDP streams, use the respective button below.");
    [_netUDPCancelButton setTitle: _NS("Cancel")];
    [_netUDPOKButton setTitle: _NS("Open")];
    [_netOpenUDPButton setTitle: _NS("Open RTP/UDP Stream")];
    [_netUDPModeLabel setStringValue: _NS("Mode")];
    [_netUDPProtocolLabel setStringValue: _NS("Protocol")];
    [_netUDPAddressLabel setStringValue: _NS("Address")];

    [[_netModeMatrix cellAtRow:0 column:0] setTitle: _NS("Unicast")];
    [[_netModeMatrix cellAtRow:1 column:0] setTitle: _NS("Multicast")];

    [_netUDPPortTextField setIntegerValue: 0];
    [_netUDPPortStepper setIntegerValue: 0];

    [_captureModePopup removeAllItems];
    [_captureModePopup addItemWithTitle: _NS("Input Devices")];
    [_captureModePopup addItemWithTitle: _NS("Screen")];
    [_screenFPSLabel setStringValue: [NSString stringWithFormat:@"%@:",_NS("Frames per Second")]];
    [_screenLabel setStringValue: [NSString stringWithFormat:@"%@:",_NS("Screen")]];
    [_screenLeftLabel setStringValue: [NSString stringWithFormat:@"%@:",_NS("Subscreen left")]];
    [_screenTopLabel setStringValue: [NSString stringWithFormat:@"%@:",_NS("Subscreen top")]];
    [_screenWidthLabel setStringValue: [NSString stringWithFormat:@"%@:",_NS("Subscreen Width")]];
    [_screenHeightLabel setStringValue: [NSString stringWithFormat:@"%@:",_NS("Subscreen Height")]];
    [_screenFollowMouseCheckbox setTitle: _NS("Follow the mouse")];
    [_screenqtkAudioCheckbox setTitle: _NS("Capture Audio")];
}

- (void)setupSubtitlesPanel
{
    int i_index;
    module_config_t * p_item;

    [self initSubtitlesPanelStrings];

    [[_fileSubDelayTextField formatter] setFormat:[NSString stringWithFormat:@"#,##0.000 %@", _NS("s")]];
    [[_fileSubFPSTextField formatter] setFormat:[NSString stringWithFormat:@"#,##0.000 %@", _NS("fps")]];
    self.fileSubFps = 1.0;

    p_item = config_FindConfig("subsdec-encoding");

    if (p_item) {
        for (int i = 0; i < p_item->list_count; i++) {
            [_fileSubEncodingPopup addItemWithTitle: NSTR(p_item->list_text[i])];
            [[_fileSubEncodingPopup lastItem] setRepresentedObject:[NSString stringWithFormat:@"%s", p_item->list.psz[i]]];
            if (p_item->value.psz && !strcmp(p_item->value.psz, p_item->list.psz[i]))
                [_fileSubEncodingPopup selectItem: [_fileSubEncodingPopup lastItem]];
        }

        if ([_fileSubEncodingPopup indexOfSelectedItem] < 0)
            [_fileSubEncodingPopup selectItemAtIndex:0];
    }

    p_item = config_FindConfig("subsdec-align");

    if (p_item) {
        for (i_index = 0; i_index < p_item->list_count; i_index++)
            [_fileSubAlignPopup addItemWithTitle: NSTR(p_item->list_text[i_index])];

        [_fileSubAlignPopup selectItemAtIndex: p_item->value.i];
    }
}

- (void)initSubtitlesPanelStrings
{
    [_fileSubCheckbox setTitle: _NS("Add Subtitle File:")];
    [_fileSubPathLabel setStringValue: _NS("Choose a file")];
    [_fileSubPathLabel setHidden: NO];
    [_fileSubPathTextField setStringValue: @""];
    [_fileSubSettingsButton setTitle: _NS("Choose...")];
    _fileSubSettingsButton.accessibilityLabel = _NS("Setup subtitle playback details");
    _fileBrowseButton.accessibilityLabel = _NS("Select a file for playback");
    [_fileSubBrowseButton setTitle: _NS("Browse...")];
    _fileSubBrowseButton.accessibilityLabel = _NS("Select a subtitle file");
    [_fileSubOverrideCheckbox setTitle: _NS("Override parameters")];
    [_fileSubDelayLabel setStringValue: _NS("Delay")];
    [_fileSubDelayStepper setEnabled: NO];
    [_fileSubFPSLabel setStringValue: _NS("FPS")];
    [_fileSubFPSStepper setEnabled: NO];
    [_fileSubEncodingLabel setStringValue: _NS("Subtitle encoding")];
    [_fileSubEncodingPopup removeAllItems];
    [_fileSubAlignLabel setStringValue: _NS("Subtitle alignment")];
    [_fileSubAlignPopup removeAllItems];
    [_fileSubOKButton setStringValue: _NS("OK")];
    _fileSubOKButton.accessibilityLabel = _NS("Dismiss the subtitle setup dialog");
    [_fileSubFontBox setTitle: _NS("Font Properties")];
    [_fileSubFileBox setTitle: _NS("Subtitle File")];
}

#pragma mark - property handling

- (void)setMRL:(NSString *)newMRL
{
    if (!newMRL)
        newMRL = @"";

    NSString * const trimmedMRL = [newMRL stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceAndNewlineCharacterSet];
    _MRL = trimmedMRL;

    dispatch_async(dispatch_get_main_queue(), ^{
        [self.mrlTextField setStringValue:self.MRL];
        if ([self.MRL length] > 0)
            [self.okButton setEnabled: YES];
        else
            [self.okButton setEnabled: NO];
    });
}

- (NSString *)MRL
{
    return _MRL;
}

#pragma mark -
#pragma mark Main Actions

- (void)openTarget:(NSString *)identifier
{
    /* check whether we already run a modal dialog */
    if ([NSApp modalWindow] != nil)
        return;

    // load window
    [self window];

    // Delay this to allow the full animation to run inside the modal event loop
    dispatch_async(dispatch_get_main_queue(), ^{
        [_tabView selectTabViewItemWithIdentifier:identifier];
        [_fileSubCheckbox setState: NSOffState];
    });

    NSModalResponse i_result = [NSApp runModalForWindow: self.window];
    [self.window close];

    // Check if dialog was canceled or stopped (NSModalResponseStop)
    if (i_result <= 0)
        return;

    [self fetchMRLcreateOptionsAndStartPlayback];
}

- (void)fetchMRLcreateOptionsAndStartPlayback
{
    NSMutableArray *options = [NSMutableArray array];
    VLCOpenInputMetadata *inputMetadata = [[VLCOpenInputMetadata alloc] init];
    inputMetadata.MRLString = [self MRL];

    if ([_fileSubCheckbox state] == NSOnState) {
        [self addSubtitleOptionsToArray:options];
    }
    if ([_fileCustomTimingCheckbox state] == NSOnState) {
        [self addTimingOptionsToArray:options];
    }
    if ([_outputCheckbox state] == NSOnState) {
        [self addStreamOutputOptionsToArray:options];
    }
    if ([_fileSlaveCheckbox state] && _fileSlavePath)
        [options addObject: [NSString stringWithFormat: @"input-slave=%@", _fileSlavePath]];
    if ([[[_tabView selectedTabViewItem] identifier] isEqualToString: VLCOpenCaptureTabViewId]) {
        if ([[[_captureModePopup selectedItem] title] isEqualToString: _NS("Screen")]) {
            [self addScreenRecordingOptionsToArray:options];
        }
        else if ([[[_captureModePopup selectedItem] title] isEqualToString: _NS("Input Devices")]) {
            [self avDeviceOptionsToArray:options];
        }
    }

    /* apply the options to our item(s) */
    inputMetadata.playbackOptions = options;

    [VLCMain.sharedInstance.playlistController addPlaylistItems:@[inputMetadata]];
}

- (void)addSubtitleOptionsToArray:(NSMutableArray *)options
{
    [options addObject: [NSString stringWithFormat: @"sub-file=%@", _subPath]];
    if ([_fileSubOverrideCheckbox state] == NSOnState) {
        [options addObject: [NSString stringWithFormat: @"sub-delay=%f", ([self fileSubDelay] * 10)]];
        [options addObject: [NSString stringWithFormat: @"sub-fps=%f", [self fileSubFps]]];
    }
    [options addObject: [NSString stringWithFormat:
                         @"subsdec-encoding=%@", [[_fileSubEncodingPopup selectedItem] representedObject]]];
    [options addObject: [NSString stringWithFormat:
                         @"subsdec-align=%li", [_fileSubAlignPopup indexOfSelectedItem]]];
}

- (void)addTimingOptionsToArray:(NSMutableArray *)options
{
    NSInteger startTime = [NSString timeInSecondsFromStringWithColons:[_fileStartTimeTextField stringValue]];
    if (startTime > 0) {
        [options addObject: [NSString stringWithFormat:@"start-time=%li", startTime]];
    }

    NSInteger stopTime = [NSString timeInSecondsFromStringWithColons:[_fileStopTimeTextField stringValue]];
    if (stopTime > 0) {
        [options addObject: [NSString stringWithFormat:@"stop-time=%li", stopTime]];
    }
}

- (void)addStreamOutputOptionsToArray:(NSMutableArray *)options
{
    for (NSString *item in [_output soutMRL])
        [options addObject: [NSString stringWithString: item]];
}

- (void)addScreenRecordingOptionsToArray:(NSMutableArray *)options
{
    NSInteger selected_index = [_screenPopup indexOfSelectedItem];
    VLCOpenDisplayInformation *displayInformation = [_displayInfos objectAtIndex:selected_index];

    [options addObject: [NSString stringWithFormat: @"screen-fps=%f", [_screenFPSTextField floatValue]]];
    [options addObject: [NSString stringWithFormat: @"screen-display-id=%i", displayInformation.displayID]];
    [options addObject: [NSString stringWithFormat: @"screen-left=%i", [_screenLeftTextField intValue]]];
    [options addObject: [NSString stringWithFormat: @"screen-top=%i", [_screenTopTextField intValue]]];
    [options addObject: [NSString stringWithFormat: @"screen-width=%i", [_screenWidthTextField intValue]]];
    [options addObject: [NSString stringWithFormat: @"screen-height=%i", [_screenHeightTextField intValue]]];
    if ([_screenFollowMouseCheckbox intValue] == YES)
        [options addObject: @"screen-follow-mouse"];
    else
        [options addObject: @"no-screen-follow-mouse"];
    if ([_screenqtkAudioCheckbox state] && _avCurrentAudioDeviceUID)
        [options addObject: [NSString stringWithFormat: @"input-slave=avaudiocapture://%@", _avCurrentAudioDeviceUID]];
}

- (void)avDeviceOptionsToArray:(NSMutableArray *)options
{
    if ([_qtkVideoCheckbox state]) {
        if ([_qtkAudioCheckbox state] && _avCurrentAudioDeviceUID)
            [options addObject: [NSString stringWithFormat: @"input-slave=avaudiocapture://%@", _avCurrentAudioDeviceUID]];
    }
}

#pragma mark - UI interaction

- (void)tabView:(NSTabView *)o_tv didSelectTabViewItem:(NSTabViewItem *)tabViewItem
{
    NSString *identifier = [tabViewItem identifier];

    if ([identifier isEqualToString: VLCOpenFileTabViewId])
        [self openFilePathChanged: nil];
    else if ([identifier isEqualToString: VLCOpenDiscTabViewId])
        [self scanOpticalMedia: nil];
    else if ([identifier isEqualToString: VLCOpenNetworkTabViewId]) {
        [self openNetInfoChanged: nil];
        [_netHTTPURLTextField selectText:nil];
    } else if ([identifier isEqualToString: VLCOpenCaptureTabViewId])
        [self openCaptureModeChanged: nil];
}

- (IBAction)expandMRLfieldAction:(id)sender
{
    if ([_mrlButton state] == NSOffState) {
        self.mrlViewHeightConstraint.animator.constant = 0;
    } else {
        self.mrlViewHeightConstraint.animator.constant = 39;
    }
}

- (void)openFileGeneric
{
    [self openFilePathChanged: nil];
    [self openTarget: VLCOpenFileTabViewId];
}

- (void)openDisc
{
    @synchronized (self) {
        [_specialMediaFolders removeAllObjects];
    }

    [self scanOpticalMedia: nil];
    [self openTarget: VLCOpenDiscTabViewId];
}

- (void)openNet
{
    [self openNetInfoChanged: nil];
    [self openTarget: VLCOpenNetworkTabViewId];
    [_netHTTPURLTextField selectText:nil];
}

- (void)openCapture
{
    [self openCaptureModeChanged: nil];
    [self openTarget: VLCOpenCaptureTabViewId];
}

- (void)openFileWithAction:(void (^)(NSArray *files))action;
{
    NSOpenPanel *openPanel = [NSOpenPanel openPanel];
    [openPanel setAllowsMultipleSelection: YES];
    [openPanel setCanChooseDirectories: YES];
    [openPanel setTitle: _NS("Open File")];
    [openPanel setPrompt: _NS("Open")];

    if ([openPanel runModal] == NSModalResponseOK) {
        NSArray *URLs = [openPanel URLs];
        NSUInteger count = [URLs count];
        NSMutableArray *values = [NSMutableArray arrayWithCapacity:count];
        NSMutableArray *array = [NSMutableArray arrayWithCapacity:count];
        for (NSURL *url in URLs)
            [values addObject: [url path]];
        [values sortUsingSelector:@selector(caseInsensitiveCompare:)];

        for (NSString *filepath in values) {
            VLCOpenInputMetadata *inputMetadata;
            inputMetadata = [VLCOpenInputMetadata inputMetaWithPath:filepath];
            if (!inputMetadata)
                continue;

            [array addObject:inputMetadata];
        }

        action(array);
    }
}

- (IBAction)outputSettings:(id)sender
{
    if (sender == self.outputCheckbox) {
        self.outputSettingsButton.enabled = self.outputCheckbox.state;
        return;
    }

    if (!b_outputNibLoaded) {
        b_outputNibLoaded = [[NSBundle mainBundle] loadNibNamed:@"StreamOutput" owner:_output topLevelObjects:nil];
    }

    [self.window beginSheet:_output.outputSheet completionHandler:nil];
}

#pragma mark -
#pragma mark File Panel

- (void)openFilePathChanged:(NSNotification *)o_notification
{
    if (_filePath && [_filePath length] > 0) {
        bool b_stream = [_fileTreatAsPipeButton state];
        BOOL b_dir = NO;

        [[NSFileManager defaultManager] fileExistsAtPath:_filePath isDirectory:&b_dir];

        char *psz_uri = vlc_path2uri([_filePath UTF8String], "file");
        if (!psz_uri) return;

        NSMutableString *mrlString = [NSMutableString stringWithUTF8String:psz_uri];
        NSRange offile = [mrlString rangeOfString:@"file"];
        free(psz_uri);

        if (b_dir)
            [mrlString replaceCharactersInRange:offile withString: @"directory"];
        else if (b_stream)
            [mrlString replaceCharactersInRange:offile withString: @"stream"];

        [_fileNameLabel setStringValue: [[NSFileManager defaultManager] displayNameAtPath:_filePath]];
        [_fileNameStubLabel setHidden: YES];
        [_fileTreatAsPipeButton setHidden: NO];
        [_fileIconWell setImage: [NSWorkspace.sharedWorkspace iconForFile: _filePath]];
        [_fileIconWell setHidden: NO];
        [self setMRL: mrlString];
    } else {
        [_fileNameLabel setStringValue: @""];
        [_fileNameStubLabel setHidden: NO];
        [_fileTreatAsPipeButton setHidden: YES];
        [_fileIconWell setImage: [NSImage imageNamed:@"generic"]];
        [self setMRL: @""];
    }
}

- (IBAction)openFileBrowse:(id)sender
{
    NSOpenPanel *openPanel = [NSOpenPanel openPanel];
    [openPanel setAllowsMultipleSelection: NO];
    [openPanel setCanChooseDirectories: YES];
    [openPanel setTitle: _NS("Open File")];
    [openPanel setPrompt: _NS("Open")];
    [openPanel beginSheetModalForWindow:[sender window] completionHandler:^(NSInteger returnCode) {
        if (returnCode == NSFileHandlingPanelOKButton) {
            self->_filePath = [[[openPanel URLs] firstObject] path];
            [self openFilePathChanged: nil];
        }
    }];
}

- (IBAction)openFileStreamChanged:(id)sender
{
    [self openFilePathChanged: nil];
}

- (IBAction)inputSlaveAction:(id)sender
{
    if (sender == _fileSlaveCheckbox)
        [_fileSelectSlaveButton setEnabled: [_fileSlaveCheckbox state]];
    else {
        NSOpenPanel *openPanel;
        openPanel = [NSOpenPanel openPanel];
        [openPanel setCanChooseFiles: YES];
        [openPanel setCanChooseDirectories: NO];
        if ([openPanel runModal] == NSModalResponseOK) {
            _fileSlavePath = [[[openPanel URLs] firstObject] path];
        }
    }
    if (_fileSlavePath && [_fileSlaveCheckbox state] == NSOnState) {
        [_fileSlaveFilenameLabel setStringValue: [[NSFileManager defaultManager] displayNameAtPath:_fileSlavePath]];
        [_fileSlaveIconWell setImage: [NSWorkspace.sharedWorkspace iconForFile: _fileSlavePath]];
    } else {
        [_fileSlaveFilenameLabel setStringValue: @""];
        [_fileSlaveIconWell setImage: NULL];
    }
}

- (IBAction)fileTimeCustomization:(id)sender
{
    BOOL b_value = [_fileCustomTimingCheckbox state];
    [_fileStartTimeTextField setEnabled: b_value];
    [_fileStartTimeLabel setEnabled: b_value];
    [_fileStopTimeTextField setEnabled: b_value];
    [_fileStopTimeLabel setEnabled: b_value];
}

#pragma mark -
#pragma mark Optical Media Panel

- (void)showOpticalMediaView:(NSView *)theView withIcon:(NSImage *)icon
{
    NSRect viewRect = [theView frame];
    [theView setFrame: NSMakeRect(233, 0, viewRect.size.width, viewRect.size.height)];
    [theView setAutoresizesSubviews: YES];

    NSView *opticalTabView = [[_tabView tabViewItemAtIndex: [_tabView indexOfTabViewItemWithIdentifier:VLCOpenDiscTabViewId]] view];
    if (_currentOpticalMediaView) {
        [[opticalTabView animator] replaceSubview: _currentOpticalMediaView with: theView];
    } else {
        [[opticalTabView animator] addSubview: theView];
    }
    _currentOpticalMediaView = theView;

    NSImageView *imageView = [[NSImageView alloc] init];
    [imageView setFrame: NSMakeRect(53, 61, 128, 128)];
    [icon setSize: NSMakeSize(128,128)];
    [imageView setImage: icon];
    if (_currentOpticalMediaIconView) {
        [[opticalTabView animator] replaceSubview: _currentOpticalMediaIconView with: imageView];
    } else {
        [[opticalTabView animator] addSubview: imageView];
    }
    _currentOpticalMediaIconView = imageView;
    [_currentOpticalMediaView setNeedsDisplay: YES];
    [_currentOpticalMediaIconView setNeedsDisplay: YES];
    [opticalTabView setNeedsDisplay: YES];
    [opticalTabView displayIfNeeded];
}

- (void)showOpticalAtPath:(VLCOpenBlockDeviceDescription *)deviceDescription
{
    NSString *diskType = deviceDescription.mediaType;
    NSString *opticalDevicePath = deviceDescription.path;
    NSString *devicePath = deviceDescription.devicePath;
    NSImage *mediaIcon = deviceDescription.mediaIcon;
    NSFileManager *fileManager = [NSFileManager defaultManager];

    if ([diskType isEqualToString: kVLCMediaDVD] || [diskType isEqualToString: kVLCMediaVideoTSFolder]) {
        [_discDVDLabel setStringValue: [[NSFileManager defaultManager] displayNameAtPath:opticalDevicePath]];
        [_discDVDwomenusLabel setStringValue: [_discDVDLabel stringValue]];

        if (!b_nodvdmenus) {
            [self setMRL: [NSString stringWithFormat: @"dvdnav://%@", devicePath]];
            [self showOpticalMediaView: _discDVDView withIcon:mediaIcon];
        } else {
            [self setMRL: [NSString stringWithFormat: @"dvdread://%@#%i:%i-", devicePath, [_discDVDwomenusTitleTextField intValue], [_discDVDwomenusChapterTextField intValue]]];
            [self showOpticalMediaView: _discDVDwomenusView withIcon:mediaIcon];
        }
    } else if ([diskType isEqualToString: kVLCMediaAudioCD]) {
        [_discAudioCDLabel setStringValue: [fileManager displayNameAtPath: opticalDevicePath]];
        [_discAudioCDTrackCountLabel setStringValue: [NSString stringWithFormat:_NS("%i tracks"), [[fileManager subpathsOfDirectoryAtPath: opticalDevicePath error:NULL] count] - 1]]; // minus .TOC.plist
        [self showOpticalMediaView: _discAudioCDView withIcon: mediaIcon];
        [self setMRL: [NSString stringWithFormat: @"cdda://%@", devicePath]];
    } else if ([diskType isEqualToString: kVLCMediaVCD]) {
        [_discVCDLabel setStringValue: [fileManager displayNameAtPath: opticalDevicePath]];
        [self showOpticalMediaView: _discVCDView withIcon: mediaIcon];
        [self setMRL: [NSString stringWithFormat: @"vcd://%@#%i:%i", devicePath, [_discVCDTitleTextField intValue], [_discVCDChapterTextField intValue]]];
    } else if ([diskType isEqualToString: kVLCMediaSVCD]) {
        [_discVCDLabel setStringValue: [fileManager displayNameAtPath: opticalDevicePath]];
        [self showOpticalMediaView: _discVCDView withIcon: mediaIcon];
        [self setMRL: [NSString stringWithFormat: @"vcd://%@@%i:%i", devicePath, [_discVCDTitleTextField intValue], [_discVCDChapterTextField intValue]]];
    } else if ([diskType isEqualToString: kVLCMediaBD] || [diskType isEqualToString: kVLCMediaBDMVFolder]) {
        [_discBDLabel setStringValue: [fileManager displayNameAtPath: opticalDevicePath]];
        [self showOpticalMediaView: _discBDView withIcon: mediaIcon];
        [self setMRL: [NSString stringWithFormat: @"bluray://%@", opticalDevicePath]];
    } else {
        msg_Warn(getIntf(), "unknown disk type, no idea what to display");

        [self showOpticalMediaView: _discNoDiscView withIcon: [NSImage imageNamed:@"NSApplicationIcon"]];
    }
}

- (VLCOpenBlockDeviceDescription *)scanPath:(NSURL *)url
{
    NSString *path = [url path];
    NSString *type = getVolumeTypeFromMountPath(path);
    NSImage *image = [NSWorkspace.sharedWorkspace iconForFile: path];
    NSString *devicePath;

    // BDMV path must not end with BDMV directory
    if ([type isEqualToString: kVLCMediaBDMVFolder]) {
        if ([[path lastPathComponent] isEqualToString: @"BDMV"]) {
            path = [path stringByDeletingLastPathComponent];
        }
    }

    if ([type isEqualToString: kVLCMediaVideoTSFolder] ||
        [type isEqualToString: kVLCMediaBD] ||
        [type isEqualToString: kVLCMediaBDMVFolder] ||
        [type isEqualToString: kVLCMediaUnknown])
        devicePath = path;
    else
        devicePath = getBSDNodeFromMountPath(path);

    VLCOpenBlockDeviceDescription *deviceDescription = [[VLCOpenBlockDeviceDescription alloc] init];
    deviceDescription.path = path;
    deviceDescription.devicePath = devicePath;
    deviceDescription.mediaType = type;
    deviceDescription.mediaIcon = image;

    return deviceDescription;
}

- (void)scanDevices
{
    @autoreleasepool {
        NSArray *mountURLs = [[NSFileManager defaultManager] mountedVolumeURLsIncludingResourceValuesForKeys:@[NSURLVolumeIsRemovableKey] options:NSVolumeEnumerationSkipHiddenVolumes];
        NSMutableArray *o_result = [NSMutableArray array];
        for (NSURL *currentURL in mountURLs) {

            NSNumber *isRemovable = nil;
            if (![currentURL getResourceValue:&isRemovable forKey:NSURLVolumeIsRemovableKey error:nil] || !isRemovable) {
                msg_Warn(getIntf(), "Cannot find removable flag for mount point");
                continue;
            }

            if (!isRemovable.boolValue)
                continue;

            [o_result addObject: [self scanPath:currentURL]];
        }

        @synchronized (self) {
            _opticalDevices = [[NSArray alloc] initWithArray: o_result];
        }

        [self updateMediaSelector:NO];
    }
}

- (void)scanSpecialPath:(NSURL *)oPath
{
    @autoreleasepool {
        VLCOpenBlockDeviceDescription *deviceDescription = [self scanPath:oPath];

        @synchronized (self) {
            [_specialMediaFolders addObject:deviceDescription];
        }

        [self updateMediaSelector:YES];
    }
}

- (void)scanOpticalMedia:(NSNotification *)o_notification
{
    [NSThread detachNewThreadSelector:@selector(scanDevices) toTarget:self withObject:nil];
}

- (void)updateMediaSelector:(BOOL)selected
{
    dispatch_async(dispatch_get_main_queue(), ^{
        [self->_allMediaDevices removeAllObjects];
        [self->_discSelectorPopup removeAllItems];

        @synchronized (self) {
            [self->_allMediaDevices addObjectsFromArray:self->_opticalDevices];
            [self->_allMediaDevices addObjectsFromArray:self->_specialMediaFolders];
        }

        NSUInteger count = [self->_allMediaDevices count];
        if (count > 0) {
            for (VLCOpenBlockDeviceDescription *deviceDescription in self->_allMediaDevices) {
                [self->_discSelectorPopup addItemWithTitle: [[NSFileManager defaultManager] displayNameAtPath:deviceDescription.path]];
            }

            if ([self->_discSelectorPopup numberOfItems] <= 1)
                [self->_discSelectorPopup setHidden: YES];
            else
                [self->_discSelectorPopup setHidden: NO];

            // select newly added media folder
            if (selected)
                [self->_discSelectorPopup selectItemAtIndex: [[self->_discSelectorPopup itemArray] count] - 1];

            // only trigger MRL update if the tab view is active
            if ([[[self->_tabView selectedTabViewItem] identifier] isEqualToString:VLCOpenDiscTabViewId])
                [self discSelectorChanged:nil];
        } else {
            msg_Dbg(getIntf(), "no optical media found");
            [self->_discSelectorPopup setHidden: YES];
            [self setMRL:@""];
            [self showOpticalMediaView: self->_discNoDiscView withIcon: [NSImage imageNamed: @"NSApplicationIcon"]];
        }
    });
}

- (IBAction)discSelectorChanged:(id)sender
{
    [self showOpticalAtPath:[_allMediaDevices objectAtIndex:[_discSelectorPopup indexOfSelectedItem]]];
}

- (IBAction)openSpecialMediaFolder:(id)sender
{
    /* this is currently for VIDEO_TS and BDMV folders */
    NSOpenPanel *openPanel = [NSOpenPanel openPanel];
    [openPanel setAllowsMultipleSelection: NO];
    [openPanel setCanChooseDirectories: YES];
    [openPanel setTitle: [sender title]];
    [openPanel setPrompt: _NS("Open")];

    /* work-around for Mountain Lion, which treats folders called "BDMV" including an item named "INDEX.BDM"
     * as a _FILE_. Don't ask, move on. There is nothing to see here */
    [openPanel setCanChooseFiles: YES];
    [openPanel setAllowedFileTypes:[NSArray arrayWithObject:@"public.directory"]];

    if ([openPanel runModal] == NSModalResponseOK) {
        NSURL *path = openPanel.URL;
        if (path) {
            [NSThread detachNewThreadSelector:@selector(scanSpecialPath:) toTarget:self withObject:path];
        }
    }
}

- (IBAction)dvdreadOptionChanged:(id)sender
{
    NSString *devicePath = [[_allMediaDevices objectAtIndex:[_discSelectorPopup indexOfSelectedItem]] devicePath];

    if (sender == _discDVDwomenusEnableMenusButton) {
        b_nodvdmenus = NO;
        [self setMRL: [NSString stringWithFormat: @"dvdnav://%@", devicePath]];
        [self showOpticalMediaView:_discDVDView withIcon:[_currentOpticalMediaIconView image]];
        return;
    }
    if (sender == _discDVDDisableMenusButton) {
        b_nodvdmenus = YES;
        [self showOpticalMediaView:_discDVDwomenusView withIcon:[_currentOpticalMediaIconView image]];
    }

    if (sender == _discDVDwomenusTitleTextField)
        [_discDVDwomenusTitleStepper setIntValue: [_discDVDwomenusTitleTextField intValue]];
    if (sender == _discDVDwomenusTitleStepper)
        [_discDVDwomenusTitleTextField setIntValue: [_discDVDwomenusTitleStepper intValue]];
    if (sender == _discDVDwomenusChapterTextField)
        [_discDVDwomenusChapterStepper setIntValue: [_discDVDwomenusChapterTextField intValue]];
    if (sender == _discDVDwomenusChapterStepper)
        [_discDVDwomenusChapterTextField setIntValue: [_discDVDwomenusChapterStepper intValue]];

    [self setMRL: [NSString stringWithFormat: @"dvdread://%@#%i:%i-", devicePath, [_discDVDwomenusTitleTextField intValue], [_discDVDwomenusChapterTextField intValue]]];
}

- (IBAction)vcdOptionChanged:(id)sender
{
    if (sender == _discVCDTitleTextField)
        [_discVCDTitleStepper setIntValue: [_discVCDTitleTextField intValue]];
    if (sender == _discVCDTitleStepper)
        [_discVCDTitleTextField setIntValue: [_discVCDTitleStepper intValue]];
    if (sender == _discVCDChapterTextField)
        [_discVCDChapterStepper setIntValue: [_discVCDChapterTextField intValue]];
    if (sender == _discVCDChapterStepper)
        [_discVCDChapterTextField setIntValue: [_discVCDChapterStepper intValue]];

    NSString *devicePath = [[_allMediaDevices objectAtIndex:[_discSelectorPopup indexOfSelectedItem]] devicePath];
    [self setMRL: [NSString stringWithFormat: @"vcd://%@@%i:%i", devicePath, [_discVCDTitleTextField intValue], [_discVCDChapterTextField intValue]]];
}

#pragma mark -
#pragma mark Network Panel

- (void)textFieldWasClicked:(NSNotification *)notification
{
    if ([notification object] == _netUDPPortTextField)
        [_netModeMatrix selectCellAtRow: 0 column: 0];
    else if ([notification object] == _netUDPMAddressTextField ||
             [notification object] == _netUDPMPortTextField)
        [_netModeMatrix selectCellAtRow: 1 column: 0];
    else
        [_netModeMatrix selectCellAtRow: 2 column: 0];

    [self openNetInfoChanged: nil];
}

- (IBAction)openNetModeChanged:(id)sender
{
    if (sender == _netModeMatrix) {
        if ([[sender selectedCell] tag] == 0)
            [self.window makeFirstResponder: _netUDPPortTextField];
        else if ([[sender selectedCell] tag] == 1)
            [self.window makeFirstResponder: _netUDPMAddressTextField];
        else
            msg_Warn(getIntf(), "Unknown sender tried to change UDP/RTP mode");
    }

    [self openNetInfoChanged: nil];
}

- (IBAction)openNetStepperChanged:(id)sender
{
    NSInteger i_tag = [sender tag];

    if (i_tag == 0) {
        [_netUDPPortTextField setIntValue: [_netUDPPortStepper intValue]];
        [NSNotificationCenter.defaultCenter postNotificationName: VLCOpenTextFieldWasClicked
                                                            object: _netUDPPortTextField];
        [self.window makeFirstResponder: _netUDPPortTextField];
    }
    else if (i_tag == 1) {
        [_netUDPMPortTextField setIntValue: [_netUDPMPortStepper intValue]];
        [NSNotificationCenter.defaultCenter postNotificationName: VLCOpenTextFieldWasClicked
                                                            object: _netUDPMPortTextField];
        [self.window makeFirstResponder: _netUDPMPortTextField];
    }

    [self openNetInfoChanged: nil];
}

- (void)openNetInfoChanged:(NSNotification *)o_notification
{
    NSString *mrlString;

    if ([_netUDPPanel isVisible]) {
        NSString *mode;
        mode = [[_netModeMatrix selectedCell] title];

        if ([mode isEqualToString: _NS("Unicast")]) {
            int port = [_netUDPPortTextField intValue];

            if ([[_netUDPProtocolMatrix selectedCell] tag] == 0)
                mrlString = @"udp://";
            else
                mrlString = @"rtp://";

            if (port > 0) {
                mrlString =
                [mrlString stringByAppendingFormat: @"@:%i", port];
            }
        }
        else if ([mode isEqualToString: _NS("Multicast")]) {
            NSString *address = [_netUDPMAddressTextField stringValue];
            int port = [_netUDPMPortTextField intValue];

            if ([[_netUDPProtocolMatrix selectedCell] tag] == 0)
                mrlString = [NSString stringWithFormat: @"udp://@%@", address];
            else
                mrlString = [NSString stringWithFormat: @"rtp://@%@", address];

            if (port > 0) {
                mrlString = [mrlString stringByAppendingFormat: @":%i", port];
            }
        }
    } else {
        mrlString = [_netHTTPURLTextField.stringValue stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceAndNewlineCharacterSet];

        // Fixup the user-provided URI
        const char *orig_uri = [mrlString UTF8String];
        if (orig_uri == NULL)
            return;
        char *fixed_uri = vlc_uri_fixup(orig_uri);
        if (fixed_uri) {
            mrlString = [[NSString alloc] initWithUTF8String:fixed_uri];
            free(fixed_uri);
        }
    }

    [self setMRL: mrlString];
}

- (IBAction)openNetUDPButtonAction:(id)sender
{
    if (sender == _netOpenUDPButton) {
        [self.window beginSheet:self.netUDPPanel
              completionHandler:nil];
        [self openNetInfoChanged:nil];
    }
    else if (sender == _netUDPCancelButton) {
        [_netUDPPanel orderOut: sender];
        [NSApp endSheet: _netUDPPanel];
    }
    else if (sender == _netUDPOKButton) {
        NSString *mrlString;
        if ([[[_netModeMatrix selectedCell] title] isEqualToString: _NS("Unicast")]) {
            int port = [_netUDPPortTextField intValue];

            if ([[_netUDPProtocolMatrix selectedCell] tag] == 0)
                mrlString = @"udp://";
            else
                mrlString = @"rtp://";

            if (port > 0) {
                mrlString = [mrlString stringByAppendingFormat: @"@:%i", port];
            }
        }
        else if ([[[_netModeMatrix selectedCell] title] isEqualToString: _NS("Multicast")]) {
            NSString *address = [_netUDPMAddressTextField stringValue];
            int port = [_netUDPMPortTextField intValue];

            if ([[_netUDPProtocolMatrix selectedCell] tag] == 0)
                mrlString = [NSString stringWithFormat: @"udp://@%@", address];
            else
                mrlString = [NSString stringWithFormat: @"rtp://@%@", address];

            if (port > 0) {
                mrlString = [mrlString stringByAppendingFormat: @":%i", port];
            }
        }
        [self setMRL: mrlString];
        [_netHTTPURLTextField setStringValue: mrlString];
        [_netUDPPanel orderOut: sender];
        [NSApp endSheet: _netUDPPanel];
    }
}

#pragma mark -
#pragma mark Capture Panel

- (IBAction)openCaptureModeChanged:(id)sender
{
    if ([[[_captureModePopup selectedItem] title] isEqualToString: _NS("Screen")]) {
        [_captureTabView selectTabViewItemAtIndex:1];

        [self setMRL: @"screen://"];
        [_screenHeightTextField setIntegerValue: config_GetInt("screen-height")];
        [_screenWidthTextField setIntegerValue: config_GetInt("screen-width")];
        [_screenFPSTextField setFloatValue: config_GetFloat("screen-fps")];
        [_screenLeftTextField setIntegerValue: config_GetInt("screen-left")];
        [_screenTopTextField setIntegerValue: config_GetInt("screen-top")];
        [_screenFollowMouseCheckbox setIntegerValue: config_GetInt("screen-follow-mouse")];

        NSInteger screenIindex = config_GetInt("screen-index");
        NSInteger displayID = config_GetInt("screen-display-id");
        unsigned int displayCount = 0;
        CGError returnedError;
        VLCOpenDisplayInformation *displayInformation;

        returnedError = CGGetOnlineDisplayList(0, NULL, &displayCount);
        if (!returnedError) {
            CGDirectDisplayID *ids;
            ids = (CGDirectDisplayID *)vlc_alloc(displayCount, sizeof(CGDirectDisplayID));
            returnedError = CGGetOnlineDisplayList(displayCount, ids, &displayCount);
            if (!returnedError) {
                [_displayInfos removeAllObjects];
                [_screenPopup removeAllItems];
                for (unsigned int i = 0; i < displayCount; i ++) {
                    displayInformation = [[VLCOpenDisplayInformation alloc] init];
                    displayInformation.displayID = ids[i];
                    NSRect displayBounds = CGDisplayBounds(displayInformation.displayID);
                    displayInformation.displayBounds = displayBounds;
                    [_screenPopup addItemWithTitle: [NSString stringWithFormat:@"Screen %d (%dx%d)", i + 1, (int)displayBounds.size.width, (int)displayBounds.size.height]];
                    [_displayInfos addObject:displayInformation];
                    if (i == 0 || displayID == displayInformation.displayID || screenIindex - 1 == i) {
                        [_screenPopup selectItemAtIndex: i];
                        [_screenLeftStepper setMaxValue: displayBounds.size.width];
                        [_screenTopStepper setMaxValue: displayBounds.size.height];
                        [_screenWidthStepper setMaxValue: displayBounds.size.width];
                        [_screenHeightStepper setMaxValue: displayBounds.size.height];
                    }
                }
            }
            free(ids);
        }
    }
    else if ([[[_captureModePopup selectedItem] title] isEqualToString: _NS("Input Devices")]) {
        [_captureTabView selectTabViewItemAtIndex:0];

        [self setMRL: @""];
        [self qtkToggleUIElements: nil];
    }
}

// Screen actions
- (void)screenFPSfieldChanged:(NSNotification *)o_notification
{
    [_screenFPSStepper setFloatValue: [_screenFPSTextField floatValue]];
    if ([[_screenFPSTextField stringValue] isEqualToString: @""])
        [_screenFPSTextField setFloatValue: 1.0];
    [self setMRL: @"screen://"];
}

- (IBAction)screenChanged:(id)sender
{
    NSInteger selected_index = [_screenPopup indexOfSelectedItem];
    if (selected_index >= [_displayInfos count])
        return;

    VLCOpenDisplayInformation *displayInformation = [_displayInfos objectAtIndex:selected_index];
    CGRect displayBounds = displayInformation.displayBounds;

    [_screenLeftStepper setMaxValue: displayBounds.size.width];
    [_screenTopStepper setMaxValue: displayBounds.size.height];
    [_screenWidthStepper setMaxValue: displayBounds.size.width];
    [_screenHeightStepper setMaxValue: displayBounds.size.height];

    [_screenqtkAudioPopup setEnabled: [_screenqtkAudioCheckbox state]];
}

- (IBAction)screenAudioChanged:(id)sender
{
    [_screenqtkAudioPopup setEnabled:_screenqtkAudioCheckbox.state];
}

// QTKit Recording actions

- (void)updateInputDevicesUrl
{
    if ([_qtkVideoCheckbox state] && _avCurrentDeviceUID)
        [self setMRL:[NSString stringWithFormat:@"avcapture://%@", _avCurrentDeviceUID]];
    else if ([_qtkAudioCheckbox state] && _avCurrentAudioDeviceUID)
        [self setMRL:[NSString stringWithFormat:@"avaudiocapture://%@", _avCurrentAudioDeviceUID]];
    else
        [self setMRL:@""];
}

- (IBAction)qtkChanged:(id)sender
{
    NSInteger selectedDevice = [_qtkVideoDevicePopup indexOfSelectedItem];
    if (selectedDevice >= _avvideoDevices.count)
        return;

    _avCurrentDeviceUID = [[(AVCaptureDevice *)[_avvideoDevices objectAtIndex:selectedDevice] uniqueID] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
    [self updateInputDevicesUrl];
}

- (IBAction)qtkAudioChanged:(id)sender
{
    NSInteger selectedDevice = [_qtkAudioDevicePopup indexOfSelectedItem];
    if (selectedDevice >= _avaudioDevices.count)
        return;

    _avCurrentAudioDeviceUID = [[(AVCaptureDevice *)[_avaudioDevices objectAtIndex:selectedDevice] uniqueID] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
    [self updateInputDevicesUrl];
}

- (IBAction)qtkToggleUIElements:(id)sender
{
    [_qtkAudioDevicePopup setEnabled:[_qtkAudioCheckbox state]];
    [_qtkVideoDevicePopup setEnabled:[_qtkVideoCheckbox state]];

    [self qtkAudioChanged:sender];
    [self qtkChanged:sender];
}

#pragma mark -
#pragma mark Subtitle Settings

- (IBAction)subsChanged:(id)sender
{
    if ([_fileSubCheckbox state] == NSOnState) {
        [_fileSubSettingsButton setEnabled:YES];
        if (_subPath) {
            [_fileSubtitlesFilenameLabel setStringValue: [[NSFileManager defaultManager] displayNameAtPath:_subPath]];
            [_fileSubtitlesIconWell setImage: [NSWorkspace.sharedWorkspace iconForFile:_subPath]];
        }
    } else {
        [_fileSubSettingsButton setEnabled:NO];
        [_fileSubtitlesFilenameLabel setStringValue: @""];
        [_fileSubtitlesIconWell setImage: NULL];
    }
}

- (IBAction)subSettings:(id)sender
{
    [[self window] beginSheet:self.fileSubSheet completionHandler:nil];
}

- (IBAction)subCloseSheet:(id)sender
{
    [self subsChanged: nil];
    [_fileSubSheet orderOut:sender];
    [NSApp endSheet: _fileSubSheet];
}

- (IBAction)subFileBrowse:(id)sender
{
    NSOpenPanel *openPanel = [NSOpenPanel openPanel];
    [openPanel setAllowsMultipleSelection: NO];
    [openPanel setTitle: _NS("Open File")];
    [openPanel setPrompt: _NS("Open")];

    if ([openPanel runModal] == NSModalResponseOK) {
        _subPath = [[[openPanel URLs] firstObject] path];
        [_fileSubtitlesFilenameLabel setStringValue: [[NSFileManager defaultManager] displayNameAtPath:_subPath]];
        [_fileSubPathTextField setStringValue: [_fileSubtitlesFilenameLabel stringValue]];
        [_fileSubPathLabel setHidden: YES];
        [_fileSubtitlesIconWell setImage: [NSWorkspace.sharedWorkspace iconForFile:_subPath]];
        [_fileSubIconView setImage: [_fileSubtitlesIconWell image]];
    } else {
        [_fileSubPathLabel setHidden: NO];
        [_fileSubPathTextField setStringValue:@""];
        [_fileSubtitlesFilenameLabel setStringValue:@""];
        [_fileSubtitlesIconWell setImage: nil];
        [_fileSubIconView setImage: nil];
    }
}

- (IBAction)subOverride:(id)sender
{
    BOOL b_state = [_fileSubOverrideCheckbox state];
    [_fileSubDelayTextField setEnabled: b_state];
    [_fileSubDelayStepper setEnabled: b_state];
    [_fileSubFPSTextField setEnabled: b_state];
    [_fileSubFPSStepper setEnabled: b_state];
}

#pragma mark -
#pragma mark Miscellaneous

- (IBAction)panelCancel:(id)sender
{
    [NSApp stopModalWithCode: 0];
}

- (IBAction)panelOk:(id)sender
{
    if ([[self MRL] length])
        [NSApp stopModalWithCode: 1];
    else
        vlc_assert_unreachable();
}

#pragma mark - audio and video device management

- (void)updateVideoDevicesAndRepresentation
{
    _avvideoDevices = [[AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]
                         arrayByAddingObjectsFromArray:[AVCaptureDevice devicesWithMediaType:AVMediaTypeMuxed]];

    [_qtkVideoDevicePopup removeAllItems];
    msg_Dbg(getIntf(), "Found %lu video capture devices", _avvideoDevices.count);

    if (_avvideoDevices.count >= 1) {
        if (!_avCurrentDeviceUID)
            _avCurrentDeviceUID = [[[AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo] uniqueID]
                                   stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];

        NSUInteger deviceCount = _avvideoDevices.count;
        for (int ivideo = 0; ivideo < deviceCount; ivideo++) {
            AVCaptureDevice *avDevice = [_avvideoDevices objectAtIndex:ivideo];
            // allow same name for multiple times
            [[_qtkVideoDevicePopup menu] addItemWithTitle:[avDevice localizedName] action:nil keyEquivalent:@""];

            if ([[[avDevice uniqueID] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] isEqualToString:_avCurrentDeviceUID])
                [_qtkVideoDevicePopup selectItemAtIndex:ivideo];
        }
    } else {
        [_qtkVideoDevicePopup addItemWithTitle: _NS("None")];
    }
}

- (void)updateAudioDevicesAndRepresentation
{
    _avaudioDevices = [[AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio]
                        arrayByAddingObjectsFromArray:[AVCaptureDevice devicesWithMediaType:AVMediaTypeMuxed]];

    [_qtkAudioDevicePopup removeAllItems];
    [_screenqtkAudioPopup removeAllItems];
    msg_Dbg(getIntf(), "Found %lu audio capture devices", _avaudioDevices.count);

    if (_avaudioDevices.count >= 1) {
        if (!_avCurrentAudioDeviceUID)
            _avCurrentAudioDeviceUID = [[[AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio] uniqueID]
                                        stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];

        NSUInteger deviceCount = _avaudioDevices.count;
        for (int iaudio = 0; iaudio < deviceCount; iaudio++) {
            AVCaptureDevice *avAudioDevice = [_avaudioDevices objectAtIndex:iaudio];

            // allow same name for multiple times
            NSString *localizedName = [avAudioDevice localizedName];
            [[_qtkAudioDevicePopup menu] addItemWithTitle:localizedName action:nil keyEquivalent:@""];
            [[_screenqtkAudioPopup menu] addItemWithTitle:localizedName action:nil keyEquivalent:@""];

            if ([[[avAudioDevice uniqueID] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] isEqualToString:_avCurrentAudioDeviceUID]) {
                [_qtkAudioDevicePopup selectItemAtIndex:iaudio];
                [_screenqtkAudioPopup selectItemAtIndex:iaudio];
            }
        }
    } else {
        [_qtkAudioDevicePopup addItemWithTitle: _NS("None")];
        [_screenqtkAudioPopup addItemWithTitle: _NS("None")];
    }
}

@end
