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