macosx: remove QTKit dependency and replace by AVFoundation
[vlc.git] / modules / gui / macosx / open.m
1 /*****************************************************************************
2  * open.m: Open dialogues for VLC's MacOS X port
3  *****************************************************************************
4  * Copyright (C) 2002-2015 VLC authors and VideoLAN
5  * $Id$
6  *
7  * Authors: Jon Lech Johansen <jon-vl@nanocrew.net>
8  *          Christophe Massiot <massiot@via.ecp.fr>
9  *          Derk-Jan Hartman <thedj@users.sourceforge.net>
10  *          Benjamin Pracht <bigben at videolan dot org>
11  *          Felix Paul K├╝hne <fkuehne at videolan dot org>
12  *
13  * This program is free software; you can redistribute it and/or modify
14  * it under the terms of the GNU General Public License as published by
15  * the Free Software Foundation; either version 2 of the License, or
16  * (at your option) any later version.
17  *
18  * This program is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  * GNU General Public License for more details.
22  *
23  * You should have received a copy of the GNU General Public License
24  * along with this program; if not, write to the Free Software
25  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
26  *****************************************************************************/
27
28 #import <stdlib.h>                                      /* malloc(), free() */
29 #import <sys/param.h>                                    /* for MAXPATHLEN */
30
31 #import "CompatibilityFixes.h"
32
33 #import <paths.h>
34 #import <IOKit/IOBSD.h>
35 #import <Cocoa/Cocoa.h>
36 #import <AVFoundation/AVFoundation.h>
37 #import <CoreMedia/CoreMedia.h>
38
39 #import "intf.h"
40 #import "VLCPlaylist.h"
41 #import "open.h"
42 #import "output.h"
43 #import "eyetv.h"
44 #import "misc.h"
45
46 #import <vlc_url.h>
47
48 struct display_info_t
49 {
50     CGRect rect;
51     CGDirectDisplayID id;
52 };
53
54 @interface VLCOpen()
55 {
56     VLCOutput *_output;
57     BOOL b_outputNibLoaded;
58     NSArray *_avvideoDevices;
59     NSArray *_avaudioDevices;
60     NSString *_avCurrentDeviceUID;
61     NSString *_avCurrentAudioDeviceUID;
62
63     BOOL b_autoplay;
64     BOOL b_nodvdmenus;
65     NSView *_currentOpticalMediaView;
66     NSImageView *_currentOpticalMediaIconView;
67     NSMutableArray *_allMediaDevices;
68     NSArray *_opticalDevices;
69     NSMutableArray *_specialMediaFolders;
70     NSString *_filePath;
71     NSView *_currentCaptureView;
72     NSString *_fileSlavePath;
73     NSString *_subPath;
74     NSString *_MRL;
75     NSMutableArray *_displayInfos;
76     VLCEyeTVController *_eyeTVController;
77 }
78
79 @property (readwrite, assign) NSString *MRL;
80
81 @end
82
83 @implementation VLCOpen
84
85 #pragma mark -
86 #pragma mark Init
87
88 - (id)init
89 {
90     self = [super initWithWindowNibName:@"Open"];
91
92     return self;
93 }
94
95
96 - (void)dealloc
97 {
98     for (int i = 0; i < [_displayInfos count]; i ++) {
99         NSValue *v = _displayInfos[i];
100         free([v pointerValue]);
101     }
102 }
103
104 - (void)windowDidLoad
105 {
106     _output = [VLCOutput new];
107
108     [self.window setCollectionBehavior: NSWindowCollectionBehaviorFullScreenAuxiliary];
109
110     [self.window setTitle: _NS("Open Source")];
111     [_mrlLabel setStringValue: _NS("Media Resource Locator (MRL)")];
112
113     [_okButton setTitle: _NS("Open")];
114     [_cancelButton setTitle: _NS("Cancel")];
115
116     [[_tabView tabViewItemAtIndex: 0] setLabel: _NS("File")];
117     [_tabView accessibilitySetOverrideValue:_NS("4 Tabs to choose between media input. Select 'File' for files, 'Disc' for optical media such as DVDs, Audio CDs or BRs, 'Network' for network streams or 'Capture' for Input Devices such as microphones or cameras, the current screen or TV streams if the EyeTV application is installed.") forAttribute:NSAccessibilityDescriptionAttribute];
118     [[_tabView tabViewItemAtIndex: 1] setLabel: _NS("Disc")];
119     [[_tabView tabViewItemAtIndex: 2] setLabel: _NS("Network")];
120     [[_tabView tabViewItemAtIndex: 3] setLabel: _NS("Capture")];
121     [_fileNameLabel setStringValue: @""];
122     [_fileNameStubLabel setStringValue: _NS("Choose a file")];
123     [_fileIconWell setImage: [NSImage imageNamed:@"generic"]];
124     [_fileBrowseButton setTitle: _NS("Browse...")];
125     [[_fileBrowseButton cell] accessibilitySetOverrideValue:_NS("Click to select a file for playback") forAttribute:NSAccessibilityDescriptionAttribute];
126     [_fileTreatAsPipeButton setTitle: _NS("Treat as a pipe rather than as a file")];
127     [_fileTreatAsPipeButton setHidden: NO];
128     [_fileSlaveCheckbox setTitle: _NS("Play another media synchronously")];
129     [_fileSelectSlaveButton setTitle: _NS("Choose...")];
130     [[_fileBrowseButton cell] accessibilitySetOverrideValue:_NS("Click to select a another file to play it in sync with the previously selected file.") forAttribute:NSAccessibilityDescriptionAttribute];
131     [_fileSlaveFilenameLabel setStringValue: @""];
132     [_fileSlaveIconWell setImage: NULL];
133     [_fileSubtitlesFilenameLabel setStringValue: @""];
134     [_fileSubtitlesIconWell setImage: NULL];
135     [_fileCustomTimingCheckbox setTitle: _NS("Custom playback")];
136     [_fileStartTimeLabel setStringValue: _NS("Start time")];
137     [_fileStartTimeTextField setStringValue: @""];
138     [_fileStopTimeLabel setStringValue: _NS("Stop time")];
139     [_fileStopTimeTextField setStringValue: @""];
140
141     [_discSelectorPopup removeAllItems];
142     [_discSelectorPopup setHidden: NO];
143     NSString *oVideoTS = _NS("Open VIDEO_TS / BDMV folder");
144     [_discNoDiscLabel setStringValue: _NS("Insert Disc")];
145     [_discNoDiscVideoTSButton setTitle: oVideoTS];
146     [_discAudioCDLabel setStringValue: _NS("Audio CD")];
147     [_discAudioCDTrackCountLabel setStringValue: @""];
148     [_discAudioCDVideoTSButton setTitle: oVideoTS];
149     [_discDVDLabel setStringValue: @""];
150     [_discDVDDisableMenusButton setTitle: _NS("Disable DVD menus")];
151     [_discDVDVideoTSButton setTitle: oVideoTS];
152     [_discDVDwomenusLabel setStringValue: @""];
153     [_discDVDwomenusEnableMenusButton setTitle: _NS("Enable DVD menus")];
154     [_discDVDwomenusVideoTSButton setTitle: oVideoTS];
155     [_discDVDwomenusTitleLabel setStringValue: _NS("Title")];
156     [_discDVDwomenusChapterLabel setStringValue: _NS("Chapter")];
157     [_discVCDTitleLabel setStringValue: _NS("Title")];
158     [_discVCDChapterLabel setStringValue: _NS("Chapter")];
159     [_discVCDVideoTSButton setTitle: oVideoTS];
160     [_discBDVideoTSButton setTitle: oVideoTS];
161
162     [_netUDPPortLabel setStringValue: _NS("Port")];
163     [_netUDPMAddressLabel setStringValue: _NS("IP Address")];
164     [_netUDPMPortLabel setStringValue: _NS("Port")];
165     [_netHTTPURLLabel setStringValue: _NS("URL")];
166     [_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.")];
167     [_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.")];
168     [_netHTTPURLTextField accessibilitySetOverrideValue:_NS("Enter a URL here to open the network stream. To open RTP or UDP streams, click on the respective button below.") forAttribute:NSAccessibilityDescriptionAttribute];
169     [_netUDPCancelButton setTitle: _NS("Cancel")];
170     [_netUDPOKButton setTitle: _NS("Open")];
171     [_netOpenUDPButton setTitle: _NS("Open RTP/UDP Stream")];
172     [_netUDPModeLabel setStringValue: _NS("Mode")];
173     [_netUDPProtocolLabel setStringValue: _NS("Protocol")];
174     [_netUDPAddressLabel setStringValue: _NS("Address")];
175
176     [[_netModeMatrix cellAtRow:0 column:0] setTitle: _NS("Unicast")];
177     [[_netModeMatrix cellAtRow:1 column:0] setTitle: _NS("Multicast")];
178
179     [_netUDPPortTextField setIntValue: config_GetInt(getIntf(), "server-port")];
180     [_netUDPPortStepper setIntValue: config_GetInt(getIntf(), "server-port")];
181
182     [_eyeTVChannelProgressBar setUsesThreadedAnimation: YES];
183
184     [_captureModePopup removeAllItems];
185     [_captureModePopup addItemWithTitle: _NS("Input Devices")];
186     [_captureModePopup addItemWithTitle: _NS("Screen")];
187     [_captureModePopup addItemWithTitle: @"EyeTV"];
188     [_screenlongLabel setStringValue: _NS("This input allows you to save, stream or display your current screen contents.")];
189     [_screenFPSLabel setStringValue: [NSString stringWithFormat:@"%@:",_NS("Frames per Second")]];
190     [_screenLabel setStringValue: [NSString stringWithFormat:@"%@:",_NS("Screen")]];
191     [_screenLeftLabel setStringValue: [NSString stringWithFormat:@"%@:",_NS("Subscreen left")]];
192     [_screenTopLabel setStringValue: [NSString stringWithFormat:@"%@:",_NS("Subscreen top")]];
193     [_screenWidthLabel setStringValue: [NSString stringWithFormat:@"%@:",_NS("Subscreen Width")]];
194     [_screenHeightLabel setStringValue: [NSString stringWithFormat:@"%@:",_NS("Subscreen Height")]];
195     [_screenFollowMouseCheckbox setTitle: _NS("Follow the mouse")];
196     [_screenqtkAudioCheckbox setTitle: _NS("Capture Audio")];
197     [_eyeTVcurrentChannelLabel setStringValue: _NS("Current channel:")];
198     [_eyeTVpreviousProgramButton setTitle: _NS("Previous Channel")];
199     [_eyeTVnextProgramButton setTitle: _NS("Next Channel")];
200     [_eyeTVChannelStatusLabel setStringValue: _NS("Retrieving Channel Info...")];
201     [_eyeTVnoInstanceLabel setStringValue: _NS("EyeTV is not launched")];
202     [_eyeTVnoInstanceLongLabel setStringValue: _NS("VLC could not connect to EyeTV.\nMake sure that you installed VLC's EyeTV plugin.")];
203     [_eyeTVlaunchEyeTVButton setTitle: _NS("Launch EyeTV now")];
204     [_eyeTVgetPluginButton setTitle: _NS("Download Plugin")];
205     [_qtkWidthLabel setStringValue: [NSString stringWithFormat:@"%@:",_NS("Image Width")]];
206     [_qtkHeightLabel setStringValue: [NSString stringWithFormat:@"%@:",_NS("Image Height")]];
207
208     // setup start / stop time fields
209     [_fileStartTimeTextField setFormatter:[[PositionFormatter alloc] init]];
210     [_fileStopTimeTextField setFormatter:[[PositionFormatter alloc] init]];
211
212     [self updateQTKVideoDevices];
213     [_qtkVideoDevicePopup removeAllItems];
214     msg_Dbg(getIntf(), "Found %lu video capture devices", _avvideoDevices.count);
215
216     if (_avvideoDevices.count >= 1) {
217         if (!_avCurrentDeviceUID)
218             _avCurrentDeviceUID = [[[AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo] uniqueID]
219                                     stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
220
221         NSUInteger deviceCount = _avvideoDevices.count;
222         for (int ivideo = 0; ivideo < deviceCount; ivideo++) {
223             AVCaptureDevice *avDevice = _avvideoDevices[ivideo];
224             // allow same name for multiple times
225             [[_qtkVideoDevicePopup menu] addItemWithTitle:[avDevice localizedName] action:nil keyEquivalent:@""];
226
227             if ([[[avDevice uniqueID] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] isEqualToString:_avCurrentDeviceUID])
228                 [_qtkVideoDevicePopup selectItemAtIndex:ivideo];
229         }
230     } else {
231         [_qtkVideoDevicePopup addItemWithTitle: _NS("None")];
232     }
233
234     [_qtkAudioDevicePopup removeAllItems];
235     [_screenqtkAudioPopup removeAllItems];
236
237     [self updateQTKAudioDevices];
238     msg_Dbg(getIntf(), "Found %lu audio capture devices", _avaudioDevices.count);
239
240     if (_avaudioDevices.count >= 1) {
241         if (!_avCurrentAudioDeviceUID)
242             _avCurrentAudioDeviceUID = [[[AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio] uniqueID]
243                                          stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
244
245         NSUInteger deviceCount = _avaudioDevices.count;
246         for (int iaudio = 0; iaudio < deviceCount; iaudio++) {
247             AVCaptureDevice *avAudioDevice = _avaudioDevices[iaudio];
248
249             // allow same name for multiple times
250             NSString *localizedName = [avAudioDevice localizedName];
251             [[_qtkAudioDevicePopup menu] addItemWithTitle:localizedName action:nil keyEquivalent:@""];
252             [[_screenqtkAudioPopup menu] addItemWithTitle:localizedName action:nil keyEquivalent:@""];
253
254             if ([[[avAudioDevice uniqueID] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] isEqualToString:_avCurrentAudioDeviceUID]) {
255                 [_qtkAudioDevicePopup selectItemAtIndex:iaudio];
256                 [_screenqtkAudioPopup selectItemAtIndex:iaudio];
257             }
258         }
259     } else {
260         [_qtkAudioDevicePopup addItemWithTitle: _NS("None")];
261         [_screenqtkAudioPopup addItemWithTitle: _NS("None")];
262     }
263
264     [self setSubPanel];
265
266     [[NSNotificationCenter defaultCenter] addObserver: self
267                                              selector: @selector(openNetInfoChanged:)
268                                                  name: NSControlTextDidChangeNotification
269                                                object: _netUDPPortTextField];
270     [[NSNotificationCenter defaultCenter] addObserver: self
271                                              selector: @selector(openNetInfoChanged:)
272                                                  name: NSControlTextDidChangeNotification
273                                                object: _netUDPMAddressTextField];
274     [[NSNotificationCenter defaultCenter] addObserver: self
275                                              selector: @selector(openNetInfoChanged:)
276                                                  name: NSControlTextDidChangeNotification
277                                                object: _netUDPMPortTextField];
278     [[NSNotificationCenter defaultCenter] addObserver: self
279                                              selector: @selector(openNetInfoChanged:)
280                                                  name: NSControlTextDidChangeNotification
281                                                object: _netHTTPURLTextField];
282
283     [[NSDistributedNotificationCenter defaultCenter] addObserver: self
284                                                         selector: @selector(eyetvChanged:)
285                                                             name: NULL
286                                                           object: @"VLCEyeTVSupport"
287                                               suspensionBehavior: NSNotificationSuspensionBehaviorDeliverImmediately];
288
289     [[NSNotificationCenter defaultCenter] addObserver: self
290                                              selector: @selector(screenFPSfieldChanged:)
291                                                  name: NSControlTextDidChangeNotification
292                                                object: _screenFPSTextField];
293
294     /* register clicks on text fields */
295     [[NSNotificationCenter defaultCenter] addObserver: self
296                                              selector: @selector(textFieldWasClicked:)
297                                                  name: VLCOpenTextFieldWasClicked
298                                                object: nil];
299
300     /* we want to be notified about removed or added media */
301     _allMediaDevices = [[NSMutableArray alloc] init];
302     _specialMediaFolders = [[NSMutableArray alloc] init];
303     _displayInfos = [[NSMutableArray alloc] init];
304     NSWorkspace *sharedWorkspace = [NSWorkspace sharedWorkspace];
305     [[sharedWorkspace notificationCenter] addObserver:self selector:@selector(scanOpticalMedia:) name:NSWorkspaceDidMountNotification object:nil];
306     [[sharedWorkspace notificationCenter] addObserver:self selector:@selector(scanOpticalMedia:) name:NSWorkspaceDidUnmountNotification object:nil];
307
308     [self qtkToggleUIElements:nil];
309     [self scanOpticalMedia:nil];
310
311     [self setMRL: @""];
312 }
313
314 - (void)setMRL:(NSString *)newMRL
315 {
316     if (!newMRL)
317         newMRL = @"";
318
319     _MRL = newMRL;
320     [self.mrlTextField performSelectorOnMainThread:@selector(setStringValue:) withObject:_MRL waitUntilDone:NO];
321     if ([_MRL length] > 0)
322         [_okButton setEnabled: YES];
323     else
324         [_okButton setEnabled: NO];
325 }
326
327 - (NSString *)MRL
328 {
329     return _MRL;
330 }
331
332 - (void)setSubPanel
333 {
334     int i_index;
335     module_config_t * p_item;
336     intf_thread_t * p_intf = getIntf();
337
338     [_fileSubCheckbox setTitle: _NS("Add Subtitle File:")];
339     [_fileSubPathLabel setStringValue: _NS("Choose a file")];
340     [_fileSubPathLabel setHidden: NO];
341     [_fileSubPathTextField setStringValue: @""];
342     [_fileSubSettingsButton setTitle: _NS("Choose...")];
343     [[_fileBrowseButton cell] accessibilitySetOverrideValue:_NS("Click to setup subtitle playback in full detail.") forAttribute:NSAccessibilityDescriptionAttribute];
344     [_fileSubBrowseButton setTitle: _NS("Browse...")];
345     [[_fileSubBrowseButton cell] accessibilitySetOverrideValue:_NS("Click to select a subtitle file.") forAttribute:NSAccessibilityDescriptionAttribute];
346     [_fileSubOverrideCheckbox setTitle: _NS("Override parameters")];
347     [_fileSubDelayLabel setStringValue: _NS("Delay")];
348     [_fileSubDelayStepper setEnabled: NO];
349     [_fileSubFPSLabel setStringValue: _NS("FPS")];
350     [_fileSubFPSStepper setEnabled: NO];
351     [_fileSubEncodingLabel setStringValue: _NS("Subtitle encoding")];
352     [_fileSubEncodingPopup removeAllItems];
353     [_fileSubSizeLabel setStringValue: _NS("Font size")];
354     [_fileSubSizePopup removeAllItems];
355     [_fileSubAlignLabel setStringValue: _NS("Subtitle alignment")];
356     [_fileSubAlignPopup removeAllItems];
357     [_fileSubOKButton setStringValue: _NS("OK")];
358     [[_fileSubOKButton cell] accessibilitySetOverrideValue:_NS("Click to dismiss the subtitle setup dialog.") forAttribute:NSAccessibilityDescriptionAttribute];
359     [_fileSubFontBox setTitle: _NS("Font Properties")];
360     [_fileSubFileBox setTitle: _NS("Subtitle File")];
361
362     p_item = config_FindConfig(VLC_OBJECT(p_intf), "subsdec-encoding");
363
364     if (p_item) {
365         for (int i = 0; i < p_item->list_count; i++) {
366             [_fileSubEncodingPopup addItemWithTitle: _NS(p_item->list_text[i])];
367             [[_fileSubEncodingPopup lastItem] setRepresentedObject:[NSString stringWithFormat:@"%s", p_item->list.psz[i]]];
368             if (p_item->value.psz && !strcmp(p_item->value.psz, p_item->list.psz[i]))
369                 [_fileSubEncodingPopup selectItem: [_fileSubEncodingPopup lastItem]];
370         }
371
372         if ([_fileSubEncodingPopup indexOfSelectedItem] < 0)
373             [_fileSubEncodingPopup selectItemAtIndex:0];
374     }
375
376     p_item = config_FindConfig(VLC_OBJECT(p_intf), "subsdec-align");
377
378     if (p_item) {
379         for (i_index = 0; i_index < p_item->list_count; i_index++)
380             [_fileSubAlignPopup addItemWithTitle: _NS(p_item->list_text[i_index])];
381
382         [_fileSubAlignPopup selectItemAtIndex: p_item->value.i];
383     }
384
385     p_item = config_FindConfig(VLC_OBJECT(p_intf), "freetype-rel-fontsize");
386
387     if (p_item) {
388         for (i_index = 0; i_index < p_item->list_count; i_index++) {
389             [_fileSubSizePopup addItemWithTitle: _NS(p_item->list_text[i_index])];
390
391             if (p_item->value.i == p_item->list.i[i_index])
392                 [_fileSubSizePopup selectItemAtIndex: i_index];
393         }
394     }
395 }
396
397 - (void)openTarget:(int)i_type
398 {
399     /* check whether we already run a modal dialog */
400     if ([NSApp modalWindow] != nil)
401         return;
402
403     // load window
404     [self window];
405
406     _eyeTVController = [[VLCEyeTVController alloc] init];
407     int i_result;
408
409     [_tabView selectTabViewItemAtIndex: i_type];
410     [_fileSubCheckbox setState: NSOffState];
411
412     i_result = [NSApp runModalForWindow: self.window];
413     [self.window close];
414
415     if (i_result) {
416         NSMutableDictionary *itemOptionsDictionary;
417         NSMutableArray *options = [NSMutableArray array];
418
419         itemOptionsDictionary = [NSMutableDictionary dictionaryWithObject: [self MRL] forKey: @"ITEM_URL"];
420         if ([_fileSubCheckbox state] == NSOnState) {
421             module_config_t * p_item;
422
423             [options addObject: [NSString stringWithFormat: @"sub-file=%@", _subPath]];
424             if ([_fileSubOverrideCheckbox state] == NSOnState) {
425                 [options addObject: [NSString stringWithFormat: @"sub-delay=%f", ([self fileSubDelay] * 10)]];
426                 [options addObject: [NSString stringWithFormat: @"sub-fps=%f", [self fileSubFps]]];
427             }
428             [options addObject: [NSString stringWithFormat:
429                                  @"subsdec-encoding=%@", [[_fileSubEncodingPopup selectedItem] representedObject]]];
430             [options addObject: [NSString stringWithFormat:
431                                  @"subsdec-align=%li", [_fileSubAlignPopup indexOfSelectedItem]]];
432
433             p_item = config_FindConfig(VLC_OBJECT(getIntf()),
434                                        "freetype-rel-fontsize");
435
436             if (p_item) {
437                 [options addObject: [NSString stringWithFormat:
438                                        @"freetype-rel-fontsize=%i",
439                                        p_item->list.i[[_fileSubSizePopup indexOfSelectedItem]]]];
440             }
441         }
442         if ([_fileCustomTimingCheckbox state] == NSOnState) {
443             NSArray *components = [[_fileStartTimeTextField stringValue] componentsSeparatedByString:@":"];
444             NSUInteger componentCount = [components count];
445             NSInteger tempValue = 0;
446             if (componentCount == 1)
447                 tempValue = [[components firstObject] intValue];
448             else if (componentCount == 2)
449                 tempValue = [[components firstObject] intValue] * 60 + [components[1] intValue];
450             else if (componentCount == 3)
451                 tempValue = [[components firstObject] intValue] * 3600 + [components[1] intValue] * 60 + [components[2] intValue];
452             if (tempValue > 0)
453                 [options addObject: [NSString stringWithFormat:@"start-time=%li", tempValue]];
454             components = [[_fileStopTimeTextField stringValue] componentsSeparatedByString:@":"];
455             componentCount = [components count];
456             if (componentCount == 1)
457                 tempValue = [[components firstObject] intValue];
458             else if (componentCount == 2)
459                 tempValue = [[components firstObject] intValue] * 60 + [components[1] intValue];
460             else if (componentCount == 3)
461                 tempValue = [[components firstObject] intValue] * 3600 + [components[1] intValue] * 60 + [components[2] intValue];
462             if (tempValue != 0)
463                 [options addObject: [NSString stringWithFormat:@"stop-time=%li", tempValue]];
464         }
465         if ([_outputCheckbox state] == NSOnState) {
466             NSArray *soutMRL = [_output soutMRL];
467             NSUInteger count = [soutMRL count];
468             for (NSUInteger i = 0 ; i < count ; i++)
469                 [options addObject: [NSString stringWithString: soutMRL[i]]];
470         }
471         if ([_fileSlaveCheckbox state] && _fileSlavePath)
472             [options addObject: [NSString stringWithFormat: @"input-slave=%@", _fileSlavePath]];
473         if ([[[_tabView selectedTabViewItem] label] isEqualToString: _NS("Capture")]) {
474             if ([[[_captureModePopup selectedItem] title] isEqualToString: _NS("Screen")]) {
475                 int selected_index = [_screenPopup indexOfSelectedItem];
476                 NSValue *v = _displayInfos[selected_index];
477                 struct display_info_t *item = (struct display_info_t *)[v pointerValue];
478
479                 [options addObject: [NSString stringWithFormat: @"screen-fps=%f", [_screenFPSTextField floatValue]]];
480                 [options addObject: [NSString stringWithFormat: @"screen-display-id=%i", item->id]];
481                 [options addObject: [NSString stringWithFormat: @"screen-left=%i", [_screenLeftTextField intValue]]];
482                 [options addObject: [NSString stringWithFormat: @"screen-top=%i", [_screenTopTextField intValue]]];
483                 [options addObject: [NSString stringWithFormat: @"screen-width=%i", [_screenWidthTextField intValue]]];
484                 [options addObject: [NSString stringWithFormat: @"screen-height=%i", [_screenHeightTextField intValue]]];
485                 if ([_screenFollowMouseCheckbox intValue] == YES)
486                     [options addObject: @"screen-follow-mouse"];
487                 else
488                     [options addObject: @"no-screen-follow-mouse"];
489                 if ([_screenqtkAudioCheckbox state] && _avCurrentAudioDeviceUID)
490                     [options addObject: [NSString stringWithFormat: @"input-slave=qtsound://%@", _avCurrentAudioDeviceUID]];
491             }
492             else if ([[[_captureModePopup selectedItem] title] isEqualToString: _NS("Input Devices")]) {
493                 if ([_qtkVideoCheckbox state]) {
494                     [options addObject: [NSString stringWithFormat: @"qtcapture-Width=%i", [_qtkWidthTextField intValue]]];
495                     [options addObject: [NSString stringWithFormat: @"qtcapture-Height=%i", [_qtkHeightTextField intValue]]];
496                     if ([_qtkAudioCheckbox state] && _avCurrentAudioDeviceUID)
497                         [options addObject: [NSString stringWithFormat: @"input-slave=qtsound://%@", _avCurrentAudioDeviceUID]];
498                 }
499             }
500         }
501
502         /* apply the options to our item(s) */
503         [itemOptionsDictionary setObject: (NSArray *)[options copy] forKey: @"ITEM_OPTIONS"];
504
505         [[[VLCMain sharedInstance] playlist] addPlaylistItems:[NSArray arrayWithObject:itemOptionsDictionary]];
506     }
507     _eyeTVController = nil;
508 }
509
510 - (IBAction)screenChanged:(id)sender
511 {
512     int selected_index = [_screenPopup indexOfSelectedItem];
513     if (selected_index >= [_displayInfos count]) return;
514
515     NSValue *v = _displayInfos[selected_index];
516     struct display_info_t *item = (struct display_info_t *)[v pointerValue];
517
518     [_screenLeftStepper setMaxValue: item->rect.size.width];
519     [_screenTopStepper setMaxValue: item->rect.size.height];
520     [_screenWidthStepper setMaxValue: item->rect.size.width];
521     [_screenHeightStepper setMaxValue: item->rect.size.height];
522
523     [_screenqtkAudioPopup setEnabled: [_screenqtkAudioCheckbox state]];
524 }
525
526 - (IBAction)qtkChanged:(id)sender
527 {
528     NSInteger selectedDevice = [_qtkVideoDevicePopup indexOfSelectedItem];
529     if (_avvideoDevices.count >= 1) {
530         CMVideoFormatDescriptionRef formatDescription = [[[_avvideoDevices[selectedDevice] formats] firstObject] formatDescription];
531         CMVideoDimensions dimensions = CMVideoFormatDescriptionGetDimensions(formatDescription);
532
533         [_qtkWidthTextField setIntValue: dimensions.width];
534         [_qtkHeightTextField setIntValue: dimensions.height];
535         [_qtkWidthStepper setIntValue: [_qtkWidthTextField intValue]];
536         [_qtkHeightStepper setIntValue: [_qtkHeightTextField intValue]];
537         _avCurrentDeviceUID = [[(AVCaptureDevice *)_avvideoDevices[selectedDevice] uniqueID] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
538     }
539 }
540
541 - (IBAction)qtkAudioChanged:(id)sender
542 {
543     NSInteger selectedDevice = [_qtkAudioDevicePopup indexOfSelectedItem];
544     if (_avaudioDevices.count >= 1) {
545         _avCurrentAudioDeviceUID = [[(AVCaptureDevice *)_avaudioDevices[selectedDevice] uniqueID] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
546     }
547     [_screenqtkAudioPopup selectItemAtIndex: selectedDevice];
548     [_qtkAudioDevicePopup selectItemAtIndex: selectedDevice];
549 }
550
551 - (IBAction)qtkToggleUIElements:(id)sender
552 {
553     [_qtkAudioDevicePopup setEnabled:[_qtkAudioCheckbox state]];
554     BOOL b_state = [_qtkVideoCheckbox state];
555     [_qtkVideoDevicePopup setEnabled:b_state];
556     [_qtkWidthTextField setEnabled:b_state];
557     [_qtkWidthStepper setEnabled:b_state];
558     [_qtkHeightTextField setEnabled:b_state];
559     [_qtkHeightStepper setEnabled:b_state];
560     [self qtkAudioChanged:sender];
561     [self qtkChanged:sender];
562     [self openCaptureModeChanged:sender];
563 }
564
565 #pragma mark -
566 #pragma mark Main Actions
567
568 - (void)tabView:(NSTabView *)o_tv didSelectTabViewItem:(NSTabViewItem *)tabViewItem
569 {
570     NSString *label = [tabViewItem label];
571
572     if ([label isEqualToString: _NS("File")])
573         [self openFilePathChanged: nil];
574     else if ([label isEqualToString: _NS("Disc")])
575         [self scanOpticalMedia: nil];
576     else if ([label isEqualToString: _NS("Network")])
577         [self openNetInfoChanged: nil];
578     else if ([label isEqualToString: _NS("Capture")])
579         [self openCaptureModeChanged: nil];
580 }
581
582 - (IBAction)expandMRLfieldAction:(id)sender
583 {
584     NSRect windowRect = [self.window frame];
585     NSRect viewRect = [_mrlView frame];
586
587     if ([_mrlButton state] == NSOffState) {
588         /* we need to collaps, restore the panel size */
589         windowRect.size.height = windowRect.size.height - viewRect.size.height;
590         windowRect.origin.y = (windowRect.origin.y + viewRect.size.height) - viewRect.size.height;
591
592         /* remove the MRL view */
593         [_mrlView removeFromSuperview];
594     } else {
595         /* we need to expand */
596         [_mrlView setFrame: NSMakeRect(0,
597                                        [_mrlButton frame].origin.y,
598                                        viewRect.size.width,
599                                        viewRect.size.height)];
600         [_mrlView setNeedsDisplay: NO];
601         [_mrlView setAutoresizesSubviews: YES];
602
603         /* enlarge panel size for MRL view */
604         windowRect.size.height = windowRect.size.height + viewRect.size.height;
605     }
606
607     [[self.window animator] setFrame: windowRect display:YES];
608
609     if ([_mrlButton state] == NSOnState)
610         [[self.window contentView] addSubview: _mrlView];
611 }
612
613 - (void)openFileGeneric
614 {
615     [self openFilePathChanged: nil];
616     [self openTarget: 0];
617 }
618
619 - (void)openDisc
620 {
621     @synchronized (self) {
622         [_specialMediaFolders removeAllObjects];
623     }
624
625     [self scanOpticalMedia: nil];
626     [self openTarget: 1];
627 }
628
629 - (void)openNet
630 {
631     [self openNetInfoChanged: nil];
632     [self openTarget: 2];
633 }
634
635 - (void)openCapture
636 {
637     [self openCaptureModeChanged: nil];
638     [self openTarget: 3];
639 }
640
641 - (void)openFile
642 {
643     NSOpenPanel *openPanel = [NSOpenPanel openPanel];
644     [openPanel setAllowsMultipleSelection: YES];
645     [openPanel setCanChooseDirectories: YES];
646     [openPanel setTitle: _NS("Open File")];
647     [openPanel setPrompt: _NS("Open")];
648
649     if ([openPanel runModal] == NSOKButton) {
650         NSArray *URLs = [openPanel URLs];
651         NSUInteger count = [URLs count];
652         NSMutableArray *values = [NSMutableArray arrayWithCapacity:count];
653         NSMutableArray *array = [NSMutableArray arrayWithCapacity:count];
654         for (NSUInteger i = 0; i < count; i++)
655             [values addObject: [URLs[i] path]];
656         [values sortUsingSelector:@selector(caseInsensitiveCompare:)];
657
658         for (NSUInteger i = 0; i < count; i++) {
659             NSDictionary *dictionary;
660             char *psz_uri = vlc_path2uri([values[i] UTF8String], "file");
661             if (!psz_uri)
662                 continue;
663             dictionary = [NSDictionary dictionaryWithObject:toNSStr(psz_uri) forKey:@"ITEM_URL"];
664             NSLog(@"dict: %@", dictionary);
665             free(psz_uri);
666             [array addObject: dictionary];
667         }
668
669         NSLog(@"adding %@", array);
670         [[[VLCMain sharedInstance] playlist] addPlaylistItems:array];
671     }
672 }
673
674 - (IBAction)outputSettings:(id)sender
675 {
676     if (sender == self.outputCheckbox) {
677         self.outputSettingsButton.enabled = self.outputCheckbox.state;
678         return;
679     }
680
681     if (!b_outputNibLoaded)
682         b_outputNibLoaded = [NSBundle loadNibNamed:@"StreamOutput" owner:_output];
683
684     [NSApp beginSheet:_output.outputSheet
685        modalForWindow:self.window
686         modalDelegate:self
687        didEndSelector:NULL
688           contextInfo:nil];
689 }
690
691 #pragma mark -
692 #pragma mark File Panel
693
694 - (void)openFilePathChanged:(NSNotification *)o_notification
695 {
696     if (_filePath && [_filePath length] > 0) {
697         bool b_stream = [_fileTreatAsPipeButton state];
698         BOOL b_dir = NO;
699
700         [[NSFileManager defaultManager] fileExistsAtPath:_filePath isDirectory:&b_dir];
701
702         char *psz_uri = vlc_path2uri([_filePath UTF8String], "file");
703         if (!psz_uri) return;
704
705         NSMutableString *mrlString = [NSMutableString stringWithUTF8String:psz_uri];
706         NSRange offile = [mrlString rangeOfString:@"file"];
707         free(psz_uri);
708
709         if (b_dir)
710             [mrlString replaceCharactersInRange:offile withString: @"directory"];
711         else if (b_stream)
712             [mrlString replaceCharactersInRange:offile withString: @"stream"];
713
714         [_fileNameLabel setStringValue: [[NSFileManager defaultManager] displayNameAtPath:_filePath]];
715         [_fileNameStubLabel setHidden: YES];
716         [_fileTreatAsPipeButton setHidden: NO];
717         [_fileIconWell setImage: [[NSWorkspace sharedWorkspace] iconForFile: _filePath]];
718         [_fileIconWell setHidden: NO];
719         [self setMRL: mrlString];
720     } else {
721         [_fileNameLabel setStringValue: @""];
722         [_fileNameStubLabel setHidden: NO];
723         [_fileTreatAsPipeButton setHidden: YES];
724         [_fileIconWell setImage: [NSImage imageNamed:@"generic"]];
725         [self setMRL: @""];
726     }
727 }
728
729 - (IBAction)openFileBrowse:(id)sender
730 {
731     NSOpenPanel *openPanel = [NSOpenPanel openPanel];
732     [openPanel setAllowsMultipleSelection: NO];
733     [openPanel setCanChooseDirectories: YES];
734     [openPanel setTitle: _NS("Open File")];
735     [openPanel setPrompt: _NS("Open")];
736     [openPanel beginSheetModalForWindow:[sender window] completionHandler:^(NSInteger returnCode) {
737         if (returnCode == NSFileHandlingPanelOKButton) {
738             _filePath = [[[openPanel URLs] firstObject] path];
739             [self openFilePathChanged: nil];
740         }
741     }];
742 }
743
744 - (IBAction)openFileStreamChanged:(id)sender
745 {
746     [self openFilePathChanged: nil];
747 }
748
749 - (IBAction)inputSlaveAction:(id)sender
750 {
751     if (sender == _fileSlaveCheckbox)
752         [_fileSelectSlaveButton setEnabled: [_fileSlaveCheckbox state]];
753     else {
754         NSOpenPanel *openPanel;
755         openPanel = [NSOpenPanel openPanel];
756         [openPanel setCanChooseFiles: YES];
757         [openPanel setCanChooseDirectories: NO];
758         if ([openPanel runModal] == NSOKButton) {
759             _fileSlavePath = [[[openPanel URLs] firstObject] path];
760         }
761     }
762     if (_fileSlavePath && [_fileSlaveCheckbox state] == NSOnState) {
763         [_fileSlaveFilenameLabel setStringValue: [[NSFileManager defaultManager] displayNameAtPath:_fileSlavePath]];
764         [_fileSlaveIconWell setImage: [[NSWorkspace sharedWorkspace] iconForFile: _fileSlavePath]];
765     } else {
766         [_fileSlaveFilenameLabel setStringValue: @""];
767         [_fileSlaveIconWell setImage: NULL];
768     }
769 }
770
771 - (IBAction)fileTimeCustomization:(id)sender
772 {
773     BOOL b_value = [_fileCustomTimingCheckbox state];
774     [_fileStartTimeTextField setEnabled: b_value];
775     [_fileStartTimeLabel setEnabled: b_value];
776     [_fileStopTimeTextField setEnabled: b_value];
777     [_fileStopTimeLabel setEnabled: b_value];
778 }
779
780 #pragma mark -
781 #pragma mark Optical Media Panel
782
783 - (void)showOpticalMediaView:(NSView *)theView withIcon:(NSImage *)icon
784 {
785     NSRect viewRect = [theView frame];
786     [theView setFrame: NSMakeRect(233, 0, viewRect.size.width, viewRect.size.height)];
787     [theView setAutoresizesSubviews: YES];
788     if (_currentOpticalMediaView) {
789         [[[[_tabView tabViewItemAtIndex: [_tabView indexOfTabViewItemWithIdentifier:@"optical"]] view] animator] replaceSubview: _currentOpticalMediaView with: theView];
790     }
791     else
792         [[[[_tabView tabViewItemAtIndex: [_tabView indexOfTabViewItemWithIdentifier:@"optical"]] view] animator] addSubview: theView];
793     _currentOpticalMediaView = theView;
794
795     NSImageView *imageView = [[NSImageView alloc] init];
796     [imageView setFrame: NSMakeRect(53, 61, 128, 128)];
797     [icon setSize: NSMakeSize(128,128)];
798     [imageView setImage: icon];
799     if (_currentOpticalMediaIconView) {
800         [[[[_tabView tabViewItemAtIndex: [_tabView indexOfTabViewItemWithIdentifier:@"optical"]] view] animator] replaceSubview: _currentOpticalMediaIconView with: imageView];
801     }
802     else
803         [[[[_tabView tabViewItemAtIndex: [_tabView indexOfTabViewItemWithIdentifier:@"optical"]] view] animator] addSubview: imageView];
804     _currentOpticalMediaIconView = imageView;
805     [_currentOpticalMediaView setNeedsDisplay: YES];
806     [_currentOpticalMediaIconView setNeedsDisplay: YES];
807     [[[_tabView tabViewItemAtIndex: [_tabView indexOfTabViewItemWithIdentifier:@"optical"]] view] setNeedsDisplay: YES];
808     [[[_tabView tabViewItemAtIndex: [_tabView indexOfTabViewItemWithIdentifier:@"optical"]] view] displayIfNeeded];
809 }
810
811 - (void)showOpticalAtPath: (NSDictionary *)valueDictionary
812 {
813     NSString *diskType = [valueDictionary objectForKey:@"mediaType"];
814     NSString *opticalDevicePath = [valueDictionary objectForKey:@"path"];
815     NSString *devicePath = [valueDictionary objectForKey:@"devicePath"];
816     NSImage *image = [valueDictionary objectForKey:@"image"];
817
818     if ([diskType isEqualToString: kVLCMediaDVD] || [diskType isEqualToString: kVLCMediaVideoTSFolder]) {
819         [_discDVDLabel setStringValue: [[NSFileManager defaultManager] displayNameAtPath:opticalDevicePath]];
820         [_discDVDwomenusLabel setStringValue: [_discDVDLabel stringValue]];
821
822         if (!b_nodvdmenus) {
823             [self setMRL: [NSString stringWithFormat: @"dvdnav://%@", devicePath]];
824             [self showOpticalMediaView: _discDVDView withIcon:image];
825         } else {
826             [self setMRL: [NSString stringWithFormat: @"dvdread://%@#%i:%i-", devicePath, [_discDVDwomenusTitleTextField intValue], [_discDVDwomenusChapterTextField intValue]]];
827             [self showOpticalMediaView: _discDVDwomenusView withIcon:image];
828         }
829     } else if ([diskType isEqualToString: kVLCMediaAudioCD]) {
830         [_discAudioCDLabel setStringValue: [[NSFileManager defaultManager] displayNameAtPath: opticalDevicePath]];
831         [_discAudioCDTrackCountLabel setStringValue: [NSString stringWithFormat:_NS("%i tracks"), [[[NSFileManager defaultManager] subpathsOfDirectoryAtPath: opticalDevicePath error:NULL] count] - 1]]; // minus .TOC.plist
832         [self showOpticalMediaView: _discAudioCDView withIcon: image];
833         [self setMRL: [NSString stringWithFormat: @"cdda://%@", devicePath]];
834     } else if ([diskType isEqualToString: kVLCMediaVCD]) {
835         [_discVCDLabel setStringValue: [[NSFileManager defaultManager] displayNameAtPath: opticalDevicePath]];
836         [self showOpticalMediaView: _discVCDView withIcon: image];
837         [self setMRL: [NSString stringWithFormat: @"vcd://%@#%i:%i", devicePath, [_discVCDTitleTextField intValue], [_discVCDChapterTextField intValue]]];
838     } else if ([diskType isEqualToString: kVLCMediaSVCD]) {
839         [_discVCDLabel setStringValue: [[NSFileManager defaultManager] displayNameAtPath: opticalDevicePath]];
840         [self showOpticalMediaView: _discVCDView withIcon: image];
841         [self setMRL: [NSString stringWithFormat: @"vcd://%@@%i:%i", devicePath, [_discVCDTitleTextField intValue], [_discVCDChapterTextField intValue]]];
842     } else if ([diskType isEqualToString: kVLCMediaBD] || [diskType isEqualToString: kVLCMediaBDMVFolder]) {
843         [_discBDLabel setStringValue: [[NSFileManager defaultManager] displayNameAtPath: opticalDevicePath]];
844         [self showOpticalMediaView: _discBDView withIcon: image];
845         [self setMRL: [NSString stringWithFormat: @"bluray://%@", opticalDevicePath]];
846     } else {
847         if (getIntf())
848             msg_Warn(getIntf(), "unknown disk type, no idea what to display");
849
850         [self showOpticalMediaView: _discNoDiscView withIcon: [NSImage imageNamed:@"NSApplicationIcon"]];
851     }
852 }
853
854 - (NSDictionary *)scanPath:(NSString *)path
855 {
856     NSString *type = [[VLCStringUtility sharedInstance] getVolumeTypeFromMountPath:path];
857     NSImage *image = [[NSWorkspace sharedWorkspace] iconForFile: path];
858     NSString *devicePath;
859
860     // BDMV path must not end with BDMV directory
861     if([type isEqualToString: kVLCMediaBDMVFolder]) {
862         if([[path lastPathComponent] isEqualToString: @"BDMV"]) {
863             path = [path stringByDeletingLastPathComponent];
864         }
865     }
866
867     if ([type isEqualToString: kVLCMediaVideoTSFolder] ||
868         [type isEqualToString: kVLCMediaBD] ||
869         [type isEqualToString: kVLCMediaBDMVFolder] ||
870         [type isEqualToString: kVLCMediaUnknown])
871         devicePath = path;
872     else
873         devicePath = [[VLCStringUtility sharedInstance] getBSDNodeFromMountPath:path];
874
875     return [NSDictionary dictionaryWithObjectsAndKeys: path, @"path",
876             devicePath, @"devicePath",
877             type, @"mediaType",
878             image, @"image", nil];
879 }
880
881 - (void)scanDevicesWithPaths:(NSArray *)paths
882 {
883     @autoreleasepool {
884         NSUInteger count = [paths count];
885         NSMutableArray *o_result = [NSMutableArray array];
886         for (NSUInteger i = 0; i < count; i++)
887             [o_result addObject: [self scanPath:paths[i]]];
888
889         @synchronized (self) {
890             _opticalDevices = [[NSArray alloc] initWithArray: o_result];
891         }
892
893         [self performSelectorOnMainThread:@selector(updateMediaSelector:) withObject:nil waitUntilDone:NO];
894     }
895 }
896
897 - (void)scanSpecialPath:(NSString *)oPath
898 {
899     @autoreleasepool {
900         NSDictionary *o_dict = [self scanPath:oPath];
901
902         @synchronized (self) {
903             [_specialMediaFolders addObject:o_dict];
904         }
905
906         [self performSelectorOnMainThread:@selector(updateMediaSelector:) withObject:[NSNumber numberWithBool:YES] waitUntilDone:NO];
907     }
908 }
909
910 - (void)scanOpticalMedia:(NSNotification *)o_notification
911 {
912     [NSThread detachNewThreadSelector:@selector(scanDevicesWithPaths:) toTarget:self withObject:[NSArray arrayWithArray:[[NSWorkspace sharedWorkspace] mountedRemovableMedia]]];
913 }
914
915 - (void)updateMediaSelector:(NSNumber *)selection
916 {
917     [_allMediaDevices removeAllObjects];
918     [_discSelectorPopup removeAllItems];
919
920     @synchronized (self) {
921         [_allMediaDevices addObjectsFromArray:_opticalDevices];
922         [_allMediaDevices addObjectsFromArray:_specialMediaFolders];
923     }
924
925     NSUInteger count = [_allMediaDevices count];
926     if (count > 0) {
927         for (NSUInteger i = 0; i < count ; i++) {
928             NSDictionary *o_dict = _allMediaDevices[i];
929             [_discSelectorPopup addItemWithTitle: [[NSFileManager defaultManager] displayNameAtPath:[o_dict objectForKey:@"path"]]];
930         }
931
932         if ([_discSelectorPopup numberOfItems] <= 1)
933             [_discSelectorPopup setHidden: YES];
934         else
935             [_discSelectorPopup setHidden: NO];
936
937         // select newly added media folder
938         if (selection && [selection boolValue])
939             [_discSelectorPopup selectItemAtIndex: [[_discSelectorPopup itemArray] count] - 1];
940
941         [self discSelectorChanged:nil];
942     } else {
943         msg_Dbg(getIntf(), "no optical media found");
944         [_discSelectorPopup setHidden: YES];
945         [self showOpticalMediaView: _discNoDiscView withIcon: [NSImage imageNamed: @"NSApplicationIcon"]];
946     }
947
948 }
949
950 - (IBAction)discSelectorChanged:(id)sender
951 {
952     [self showOpticalAtPath:_allMediaDevices[[_discSelectorPopup indexOfSelectedItem]]];
953 }
954
955 - (IBAction)openSpecialMediaFolder:(id)sender
956 {
957     /* this is currently for VIDEO_TS and BDMV folders */
958     NSOpenPanel *openPanel = [NSOpenPanel openPanel];
959     [openPanel setAllowsMultipleSelection: NO];
960     [openPanel setCanChooseDirectories: YES];
961     [openPanel setTitle: [sender title]];
962     [openPanel setPrompt: _NS("Open")];
963
964     /* work-around for Mountain Lion, which treats folders called "BDMV" including an item named "INDEX.BDM"
965      * as a _FILE_. Don't ask, move on. There is nothing to see here */
966     [openPanel setCanChooseFiles: YES];
967     [openPanel setAllowedFileTypes:[NSArray arrayWithObject:@"public.directory"]];
968
969     if ([openPanel runModal] == NSOKButton) {
970         NSString *oPath = [[[openPanel URLs] firstObject] path];
971         if ([oPath length] > 0) {
972             [NSThread detachNewThreadSelector:@selector(scanSpecialPath:) toTarget:self withObject:oPath];
973         }
974     }
975 }
976
977 - (IBAction)dvdreadOptionChanged:(id)sender
978 {
979     NSString *devicePath = [_allMediaDevices[[_discSelectorPopup indexOfSelectedItem]] objectForKey:@"devicePath"];
980
981     if (sender == _discDVDwomenusEnableMenusButton) {
982         b_nodvdmenus = NO;
983         [self setMRL: [NSString stringWithFormat: @"dvdnav://%@", devicePath]];
984         [self showOpticalMediaView:_discDVDView withIcon:[_currentOpticalMediaIconView image]];
985         return;
986     }
987     if (sender == _discDVDDisableMenusButton) {
988         b_nodvdmenus = YES;
989         [self showOpticalMediaView:_discDVDwomenusView withIcon:[_currentOpticalMediaIconView image]];
990     }
991
992     if (sender == _discDVDwomenusTitleTextField)
993         [_discDVDwomenusTitleStepper setIntValue: [_discDVDwomenusTitleTextField intValue]];
994     if (sender == _discDVDwomenusTitleStepper)
995         [_discDVDwomenusTitleTextField setIntValue: [_discDVDwomenusTitleStepper intValue]];
996     if (sender == _discDVDwomenusChapterTextField)
997         [_discDVDwomenusChapterStepper setIntValue: [_discDVDwomenusChapterTextField intValue]];
998     if (sender == _discDVDwomenusChapterStepper)
999         [_discDVDwomenusChapterTextField setIntValue: [_discDVDwomenusChapterStepper intValue]];
1000
1001     [self setMRL: [NSString stringWithFormat: @"dvdread://%@#%i:%i-", devicePath, [_discDVDwomenusTitleTextField intValue], [_discDVDwomenusChapterTextField intValue]]];
1002 }
1003
1004 - (IBAction)vcdOptionChanged:(id)sender
1005 {
1006     if (sender == _discVCDTitleTextField)
1007         [_discVCDTitleStepper setIntValue: [_discVCDTitleTextField intValue]];
1008     if (sender == _discVCDTitleStepper)
1009         [_discVCDTitleTextField setIntValue: [_discVCDTitleStepper intValue]];
1010     if (sender == _discVCDChapterTextField)
1011         [_discVCDChapterStepper setIntValue: [_discVCDChapterTextField intValue]];
1012     if (sender == _discVCDChapterStepper)
1013         [_discVCDChapterTextField setIntValue: [_discVCDChapterStepper intValue]];
1014
1015     NSString *devicePath = [_allMediaDevices[[_discSelectorPopup indexOfSelectedItem]] objectForKey:@"devicePath"];
1016     [self setMRL: [NSString stringWithFormat: @"vcd://%@@%i:%i", devicePath, [_discVCDTitleTextField intValue], [_discVCDChapterTextField intValue]]];
1017 }
1018
1019 #pragma mark -
1020 #pragma mark Network Panel
1021
1022 - (void)textFieldWasClicked:(NSNotification *)notification
1023 {
1024     if ([notification object] == _netUDPPortTextField)
1025         [_netModeMatrix selectCellAtRow: 0 column: 0];
1026     else if ([notification object] == _netUDPMAddressTextField ||
1027              [notification object] == _netUDPMPortTextField)
1028         [_netModeMatrix selectCellAtRow: 1 column: 0];
1029     else
1030         [_netModeMatrix selectCellAtRow: 2 column: 0];
1031
1032     [self openNetInfoChanged: nil];
1033 }
1034
1035 - (IBAction)openNetModeChanged:(id)sender
1036 {
1037     if (sender == _netModeMatrix) {
1038         if ([[sender selectedCell] tag] == 0)
1039             [self.window makeFirstResponder: _netUDPPortTextField];
1040         else if ([[sender selectedCell] tag] == 1)
1041             [self.window makeFirstResponder: _netUDPMAddressTextField];
1042         else
1043             msg_Warn(getIntf(), "Unknown sender tried to change UDP/RTP mode");
1044     }
1045
1046     [self openNetInfoChanged: nil];
1047 }
1048
1049 - (IBAction)openNetStepperChanged:(id)sender
1050 {
1051     int i_tag = [sender tag];
1052
1053     if (i_tag == 0) {
1054         [_netUDPPortTextField setIntValue: [_netUDPPortStepper intValue]];
1055         [[NSNotificationCenter defaultCenter] postNotificationName: VLCOpenTextFieldWasClicked
1056                                                             object: _netUDPPortTextField];
1057         [self.window makeFirstResponder: _netUDPPortTextField];
1058     }
1059     else if (i_tag == 1) {
1060         [_netUDPMPortTextField setIntValue: [_netUDPMPortStepper intValue]];
1061         [[NSNotificationCenter defaultCenter] postNotificationName: VLCOpenTextFieldWasClicked
1062                                                             object: _netUDPMPortTextField];
1063         [self.window makeFirstResponder: _netUDPMPortTextField];
1064     }
1065
1066     [self openNetInfoChanged: nil];
1067 }
1068
1069 - (void)openNetInfoChanged:(NSNotification *)o_notification
1070 {
1071     NSString *mrlString;
1072
1073     if ([_netUDPPanel isVisible]) {
1074         NSString *mode;
1075         mode = [[_netModeMatrix selectedCell] title];
1076
1077         if ([mode isEqualToString: _NS("Unicast")]) {
1078             int port = [_netUDPPortTextField intValue];
1079
1080             if ([[_netUDPProtocolMatrix selectedCell] tag] == 0)
1081                 mrlString = @"udp://";
1082             else
1083                 mrlString = @"rtp://";
1084
1085             if (port != config_GetInt(getIntf(), "server-port")) {
1086                 mrlString =
1087                 [mrlString stringByAppendingFormat: @"@:%i", port];
1088             }
1089         }
1090         else if ([mode isEqualToString: _NS("Multicast")]) {
1091             NSString *oAddress = [_netUDPMAddressTextField stringValue];
1092             int iPort = [_netUDPMPortTextField intValue];
1093
1094             if ([[_netUDPProtocolMatrix selectedCell] tag] == 0)
1095                 mrlString = [NSString stringWithFormat: @"udp://@%@", oAddress];
1096             else
1097                 mrlString = [NSString stringWithFormat: @"rtp://@%@", oAddress];
1098
1099             if (iPort != config_GetInt(getIntf(), "server-port")) {
1100                 mrlString =
1101                 [mrlString stringByAppendingFormat: @":%i", iPort];
1102             }
1103         }
1104     } else
1105         mrlString = [_netHTTPURLTextField stringValue];
1106
1107     [self setMRL: mrlString];
1108 }
1109
1110 - (IBAction)openNetUDPButtonAction:(id)sender
1111 {
1112     if (sender == _netOpenUDPButton) {
1113         [NSApp beginSheet: self.netUDPPanel
1114            modalForWindow: self.window
1115             modalDelegate: self
1116            didEndSelector: NULL
1117               contextInfo: nil];
1118         [self openNetInfoChanged:nil];
1119     }
1120     else if (sender == _netUDPCancelButton) {
1121         [_netUDPPanel orderOut: sender];
1122         [NSApp endSheet: _netUDPPanel];
1123     }
1124     else if (sender == _netUDPOKButton) {
1125         NSString *mrlString;
1126         if ([[[_netModeMatrix selectedCell] title] isEqualToString: _NS("Unicast")]) {
1127             int port = [_netUDPPortTextField intValue];
1128
1129             if ([[_netUDPProtocolMatrix selectedCell] tag] == 0)
1130                 mrlString = @"udp://";
1131             else
1132                 mrlString = @"rtp://";
1133
1134             if (port != config_GetInt(getIntf(), "server-port")) {
1135                 mrlString =
1136                 [mrlString stringByAppendingFormat: @"@:%i", port];
1137             }
1138         }
1139         else if ([[[_netModeMatrix selectedCell] title] isEqualToString: _NS("Multicast")]) {
1140             NSString *oAddress = [_netUDPMAddressTextField stringValue];
1141             int iPort = [_netUDPMPortTextField intValue];
1142
1143             if ([[_netUDPProtocolMatrix selectedCell] tag] == 0)
1144                 mrlString = [NSString stringWithFormat: @"udp://@%@", oAddress];
1145             else
1146                 mrlString = [NSString stringWithFormat: @"rtp://@%@", oAddress];
1147
1148             if (iPort != config_GetInt(getIntf(), "server-port")) {
1149                 mrlString =
1150                 [mrlString stringByAppendingFormat: @":%i", iPort];
1151             }
1152         }
1153         [self setMRL: mrlString];
1154         [_netHTTPURLTextField setStringValue: mrlString];
1155         [_netUDPPanel orderOut: sender];
1156         [NSApp endSheet: _netUDPPanel];
1157     }
1158 }
1159
1160 #pragma mark -
1161 #pragma mark Capture Panel
1162
1163 - (void)showCaptureView: theView
1164 {
1165     NSRect viewRect = [theView frame];
1166     [theView setFrame: NSMakeRect(0, -10, viewRect.size.width, viewRect.size.height)];
1167     [theView setAutoresizesSubviews: YES];
1168     if (_currentCaptureView) {
1169         [[[[_tabView tabViewItemAtIndex: 3] view] animator] replaceSubview: _currentCaptureView with: theView];
1170     } else {
1171         [[[[_tabView tabViewItemAtIndex: 3] view] animator] addSubview: theView];
1172     }
1173     _currentCaptureView = theView;
1174 }
1175
1176 - (IBAction)openCaptureModeChanged:(id)sender
1177 {
1178     intf_thread_t * p_intf = getIntf();
1179
1180     if ([[[_captureModePopup selectedItem] title] isEqualToString: @"EyeTV"]) {
1181         if ([_eyeTVController eyeTVRunning] == YES) {
1182             if ([_eyeTVController deviceConnected] == YES) {
1183                 [self showCaptureView: _eyeTVrunningView];
1184                 [self setupChannelInfo];
1185             }
1186             else
1187                 [self setEyeTVUnconnected];
1188         }
1189         else
1190             [self showCaptureView: _eyeTVnotLaunchedView];
1191         [self setMRL: @""];
1192     }
1193     else if ([[[_captureModePopup selectedItem] title] isEqualToString: _NS("Screen")]) {
1194         [self showCaptureView: _screenView];
1195         [self setMRL: @"screen://"];
1196         [_screenHeightTextField setIntValue: config_GetInt(p_intf, "screen-height")];
1197         [_screenWidthTextField setIntValue: config_GetInt(p_intf, "screen-width")];
1198         [_screenFPSTextField setFloatValue: config_GetFloat(p_intf, "screen-fps")];
1199         [_screenLeftTextField setIntValue: config_GetInt(p_intf, "screen-left")];
1200         [_screenTopTextField setIntValue: config_GetInt(p_intf, "screen-top")];
1201         [_screenFollowMouseCheckbox setIntValue: config_GetInt(p_intf, "screen-follow-mouse")];
1202
1203         int screenIindex = config_GetInt(p_intf, "screen-index");
1204         int displayID = config_GetInt(p_intf, "screen-display-id");
1205         unsigned int displayCount = 0;
1206         CGError returnedError;
1207         struct display_info_t *item;
1208         NSValue *v;
1209
1210         returnedError = CGGetOnlineDisplayList(0, NULL, &displayCount);
1211         if (!returnedError) {
1212             CGDirectDisplayID *ids;
1213             ids = (CGDirectDisplayID *)malloc(displayCount * sizeof(CGDirectDisplayID));
1214             returnedError = CGGetOnlineDisplayList(displayCount, ids, &displayCount);
1215             if (!returnedError) {
1216                 NSUInteger displayInfoCount = [_displayInfos count];
1217                 for (NSUInteger i = 0; i < displayInfoCount; i ++) {
1218                     v = _displayInfos[i];
1219                     free([v pointerValue]);
1220                 }
1221                 [_displayInfos removeAllObjects];
1222                 [_screenPopup removeAllItems];
1223                 for (unsigned int i = 0; i < displayCount; i ++) {
1224                     item = (struct display_info_t *)malloc(sizeof(struct display_info_t));
1225                     item->id = ids[i];
1226                     item->rect = CGDisplayBounds(item->id);
1227                     [_screenPopup addItemWithTitle: [NSString stringWithFormat:@"Screen %d (%dx%d)", i + 1, (int)item->rect.size.width, (int)item->rect.size.height]];
1228                     v = [NSValue valueWithPointer:item];
1229                     [_displayInfos addObject:v];
1230                     if (i == 0 || displayID == item->id || screenIindex - 1 == i) {
1231                         [_screenPopup selectItemAtIndex: i];
1232                         [_screenLeftStepper setMaxValue: item->rect.size.width];
1233                         [_screenTopStepper setMaxValue: item->rect.size.height];
1234                         [_screenWidthStepper setMaxValue: item->rect.size.width];
1235                         [_screenHeightStepper setMaxValue: item->rect.size.height];
1236                     }
1237                 }
1238             }
1239             free(ids);
1240         }
1241     }
1242     else if ([[[_captureModePopup selectedItem] title] isEqualToString: _NS("Input Devices")]) {
1243         [self showCaptureView: _qtkView];
1244         if ([_qtkWidthTextField intValue] <= 0)
1245             [self qtkChanged:nil];
1246
1247         [self qtkAudioChanged:nil];
1248
1249         [self setMRL: @""];
1250
1251         if ([_qtkVideoCheckbox state] && _avCurrentDeviceUID)
1252             [self setMRL:[NSString stringWithFormat:@"qtcapture://%@", _avCurrentDeviceUID]];
1253         else if ([_qtkAudioCheckbox state] && _avCurrentAudioDeviceUID)
1254             [self setMRL:[NSString stringWithFormat:@"qtsound://%@", _avCurrentAudioDeviceUID]];
1255     }
1256 }
1257
1258 - (void)screenFPSfieldChanged:(NSNotification *)o_notification
1259 {
1260     [_screenFPSStepper setFloatValue: [_screenFPSTextField floatValue]];
1261     if ([[_screenFPSTextField stringValue] isEqualToString: @""])
1262         [_screenFPSTextField setFloatValue: 1.0];
1263     [self setMRL: @"screen://"];
1264 }
1265
1266 - (IBAction)eyetvSwitchChannel:(id)sender
1267 {
1268     if (sender == _eyeTVnextProgramButton) {
1269         int chanNum = [_eyeTVController switchChannelUp: YES];
1270         [_eyeTVchannelsPopup selectItemWithTag:chanNum];
1271         [self setMRL: [NSString stringWithFormat:@"eyetv:// :eyetv-channel=%d", chanNum]];
1272     } else if (sender == _eyeTVpreviousProgramButton) {
1273         int chanNum = [_eyeTVController switchChannelUp: NO];
1274         [_eyeTVchannelsPopup selectItemWithTag:chanNum];
1275         [self setMRL: [NSString stringWithFormat:@"eyetv:// :eyetv-channel=%d", chanNum]];
1276     } else if (sender == _eyeTVchannelsPopup) {
1277         int chanNum = [[sender selectedItem] tag];
1278         [_eyeTVController setChannel:chanNum];
1279         [self setMRL: [NSString stringWithFormat:@"eyetv:// :eyetv-channel=%d", chanNum]];
1280     } else
1281         msg_Err(getIntf(), "eyetvSwitchChannel sent by unknown object");
1282 }
1283
1284 - (IBAction)eyetvLaunch:(id)sender
1285 {
1286     [_eyeTVController launchEyeTV];
1287 }
1288
1289 - (IBAction)eyetvGetPlugin:(id)sender
1290 {
1291     [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: @"http://www.videolan.org/vlc/eyetv"]];
1292 }
1293
1294 - (void)eyetvChanged:(NSNotification *)notification
1295 {
1296     if ([[notification name] isEqualToString: @"DeviceAdded"]) {
1297         msg_Dbg(getIntf(), "eyetv device was added");
1298         [self showCaptureView: _eyeTVrunningView];
1299         [self setupChannelInfo];
1300     } else if ([[notification name] isEqualToString: @"DeviceRemoved"]) {
1301         /* leave the channel selection like that,
1302          * switch to our "no device" tab */
1303         msg_Dbg(getIntf(), "eyetv device was removed");
1304         [self setEyeTVUnconnected];
1305     } else if ([[notification name] isEqualToString: @"PluginQuit"]) {
1306         /* switch to the "launch eyetv" tab */
1307         msg_Dbg(getIntf(), "eyetv was terminated");
1308         [self showCaptureView: _eyeTVnotLaunchedView];
1309     } else if ([[notification name] isEqualToString: @"PluginInit"]) {
1310         /* we got no device yet */
1311         msg_Dbg(getIntf(), "eyetv was launched, no device yet");
1312         [self setEyeTVUnconnected];
1313     }
1314 }
1315
1316 - (void)setEyeTVUnconnected
1317 {
1318     [_captureLabel setStringValue: _NS("No device is selected")];
1319     [_captureLongLabel setStringValue: _NS("No device is selected.\n\nChoose available device in above pull-down menu.\n")];
1320     [_captureLabel displayIfNeeded];
1321     [_captureLongLabel displayIfNeeded];
1322     [self showCaptureView: _captureView];
1323 }
1324
1325 /* little helper method, since this code needs to be run by multiple objects */
1326 - (void)setupChannelInfo
1327 {
1328     /* set up channel selection */
1329     [_eyeTVchannelsPopup removeAllItems];
1330     [_eyeTVChannelProgressBar setHidden: NO];
1331     [_eyeTVChannelProgressBar startAnimation:self];
1332     [_eyeTVChannelStatusLabel setStringValue: _NS("Retrieving Channel Info...")];
1333     [_eyeTVChannelStatusLabel setHidden: NO];
1334
1335     /* retrieve info */
1336     NSEnumerator *channels = [_eyeTVController allChannels];
1337     int x = -2;
1338     [[[_eyeTVchannelsPopup menu] addItemWithTitle: _NS("Composite input")
1339                                            action: nil
1340                                     keyEquivalent: @""] setTag:x++];
1341     [[[_eyeTVchannelsPopup menu] addItemWithTitle: _NS("S-Video input")
1342                                            action: nil
1343                                     keyEquivalent: @""] setTag:x++];
1344     if (channels) {
1345         NSString *channel;
1346         [[_eyeTVchannelsPopup menu] addItem: [NSMenuItem separatorItem]];
1347         while ((channel = [channels nextObject]) != nil)
1348         /* we have to add items this way, because we accept duplicates
1349          * additionally, we save a bit of time */
1350             [[[_eyeTVchannelsPopup menu] addItemWithTitle: channel action: nil keyEquivalent: @""] setTag:++x];
1351
1352         /* make Tuner the default */
1353         [_eyeTVchannelsPopup selectItemWithTag:[_eyeTVController channel]];
1354     }
1355
1356     /* clean up GUI */
1357     [_eyeTVChannelProgressBar stopAnimation:self];
1358     [_eyeTVChannelProgressBar setHidden: YES];
1359     [_eyeTVChannelStatusLabel setHidden: YES];
1360 }
1361
1362 #pragma mark -
1363 #pragma mark Subtitle Settings
1364
1365 - (IBAction)subsChanged:(id)sender
1366 {
1367     if ([_fileSubCheckbox state] == NSOnState) {
1368         [_fileSubSettingsButton setEnabled:YES];
1369         if (_subPath) {
1370             [_fileSubtitlesFilenameLabel setStringValue: [[NSFileManager defaultManager] displayNameAtPath:_subPath]];
1371             [_fileSubtitlesIconWell setImage: [[NSWorkspace sharedWorkspace] iconForFile:_subPath]];
1372         }
1373     } else {
1374         [_fileSubSettingsButton setEnabled:NO];
1375         [_fileSubtitlesFilenameLabel setStringValue: @""];
1376         [_fileSubtitlesIconWell setImage: NULL];
1377     }
1378 }
1379
1380 - (IBAction)subSettings:(id)sender
1381 {
1382     [NSApp beginSheet: self.fileSubSheet
1383        modalForWindow: [sender window]
1384         modalDelegate: self
1385        didEndSelector: NULL
1386           contextInfo: nil];
1387 }
1388
1389 - (IBAction)subCloseSheet:(id)sender
1390 {
1391     [self subsChanged: nil];
1392     [_fileSubSheet orderOut:sender];
1393     [NSApp endSheet: _fileSubSheet];
1394 }
1395
1396 - (IBAction)subFileBrowse:(id)sender
1397 {
1398     NSOpenPanel *openPanel = [NSOpenPanel openPanel];
1399     [openPanel setAllowsMultipleSelection: NO];
1400     [openPanel setTitle: _NS("Open File")];
1401     [openPanel setPrompt: _NS("Open")];
1402
1403     if ([openPanel runModal] == NSOKButton) {
1404         _subPath = [[[openPanel URLs] firstObject] path];
1405         [_fileSubtitlesFilenameLabel setStringValue: [[NSFileManager defaultManager] displayNameAtPath:_subPath]];
1406         [_fileSubPathTextField setStringValue: [_fileSubtitlesFilenameLabel stringValue]];
1407         [_fileSubPathLabel setHidden: YES];
1408         [_fileSubtitlesIconWell setImage: [[NSWorkspace sharedWorkspace] iconForFile:_subPath]];
1409         [_fileSubIconView setImage: [_fileSubtitlesIconWell image]];
1410     } else {
1411         [_fileSubPathLabel setHidden: NO];
1412         [_fileSubPathTextField setStringValue:@""];
1413         [_fileSubtitlesFilenameLabel setStringValue:@""];
1414         [_fileSubtitlesIconWell setImage: nil];
1415         [_fileSubIconView setImage: nil];
1416     }
1417 }
1418
1419 - (IBAction)subOverride:(id)sender
1420 {
1421     BOOL b_state = [_fileSubOverrideCheckbox state];
1422     [_fileSubDelayTextField setEnabled: b_state];
1423     [_fileSubDelayStepper setEnabled: b_state];
1424     [_fileSubFPSTextField setEnabled: b_state];
1425     [_fileSubFPSStepper setEnabled: b_state];
1426 }
1427
1428 #pragma mark -
1429 #pragma mark Miscellaneous
1430
1431 - (IBAction)panelCancel:(id)sender
1432 {
1433     [NSApp stopModalWithCode: 0];
1434 }
1435
1436 - (IBAction)panelOk:(id)sender
1437 {
1438     if ([[self MRL] length])
1439         [NSApp stopModalWithCode: 1];
1440     else
1441         NSBeep();
1442 }
1443
1444 - (void)updateQTKVideoDevices
1445 {
1446     _avvideoDevices = [[AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]
1447                          arrayByAddingObjectsFromArray:[AVCaptureDevice devicesWithMediaType:AVMediaTypeMuxed]];
1448 }
1449
1450 - (void)updateQTKAudioDevices
1451 {
1452     _avaudioDevices = [[AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio]
1453                         arrayByAddingObjectsFromArray:[AVCaptureDevice devicesWithMediaType:AVMediaTypeMuxed]];
1454 }
1455
1456 @end