1/*****************************************************************************
2* VLCSimplePrefsController.m: Simple Preferences for Mac OS X
3*****************************************************************************
4* Copyright (C) 2008-2018 VLC authors and VideoLAN
5* $Id: 948ebef0a7e91efe7e386495b4321f59ecdcc2fc $
6*
7* Authors: Felix Paul Kühne <fkuehne at videolan dot org>
8*
9* This program is free software; you can redistribute it and/or modify
10* it under the terms of the GNU General Public License as published by
11* the Free Software Foundation; either version 2 of the License, or
12* (at your option) any later version.
13*
14* This program is distributed in the hope that it will be useful,
15* but WITHOUT ANY WARRANTY; without even the implied warranty of
16* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17* GNU General Public License for more details.
18*
19* You should have received a copy of the GNU General Public License
20* along with this program; if not, write to the Free Software
21* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22*****************************************************************************/
23#ifdef HAVE_CONFIG_H
24# include "config.h"
25#endif
26
27#import "CompatibilityFixes.h"
28#import "VLCSimplePrefsController.h"
29#import "prefs.h"
30#import <vlc_actions.h>
31#import <vlc_interface.h>
32#import <vlc_dialog.h>
33#import <vlc_modules.h>
34#import <vlc_plugin.h>
35#import <vlc_config_cat.h>
36#import "misc.h"
37#import "VLCMain.h"
38#import "VLCMain+OldPrefs.h"
39#import "AppleRemote.h"
40#import "VLCCoreInteraction.h"
41#import "NSScreen+VLCAdditions.h"
42
43#ifdef HAVE_SPARKLE
44#import <Sparkle/Sparkle.h>                        //for o_intf_last_updateLabel
45#endif
46
47
48static struct {
49    const char iso[6];
50    const char name[34];
51    BOOL isRightToLeft;
52
53} const language_map[] = {
54    { "auto",  N_("Auto"),              NO },
55    { "en",    "American English",      NO },
56    { "ar",    "عربي",                  YES },
57    { "an",    "Aragonés",              NO },
58    { "as_IN", "অসমীয়া",                 NO },
59    { "ast",   "Asturianu",             NO },
60    { "be",    "беларуская мова",       NO },
61    { "brx",   "बर'/बड़",                 NO },
62    { "bn",    "বাংলা",                   NO },
63    { "pt_BR", "Português Brasileiro",  NO },
64    { "en_GB", "British English",       NO },
65    { "el",    "Νέα Ελληνικά",          NO },
66    { "bg",    "български език",        NO },
67    { "ca",    "Català",                NO },
68    { "zh_TW", "正體中文",                NO },
69    { "co",    "Corsu",                 NO },
70    { "cs",    "Čeština",               NO },
71    { "cy",    "Cymraeg",               NO },
72    { "da",    "Dansk",                 NO },
73    { "nl",    "Nederlands",            NO },
74    { "fi",    "Suomi",                 NO },
75    { "et",    "eesti keel",            NO },
76    { "eu",    "Euskara",               NO },
77    { "fr",    "Français",              NO },
78    { "ga",    "Gaeilge",               NO },
79    { "gd",    "Gàidhlig",              NO },
80    { "gl",    "Galego",                NO },
81    { "gu",    "ગુજરાતી",                 NO },
82    { "de",    "Deutsch",               NO },
83    { "he",    "עברית",                 YES },
84    { "hr",    "hrvatski",              NO },
85    { "kn",    "ಕನ್ನಡ",                   NO },
86    { "lv",    "Latviešu valoda",       NO },
87    { "hu",    "Magyar",                NO },
88    { "mr",    "मराठी",                   NO },
89    { "is",    "íslenska",              NO },
90    { "id",    "Bahasa Indonesia",      NO },
91    { "it",    "Italiano",              NO },
92    { "ja",    "日本語",                 NO },
93    { "ko",    "한국어",                  NO },
94    { "lt",    "lietuvių",              NO },
95    { "ms",    "Melayu",                NO },
96    { "nb",    "Bokmål",                NO },
97    { "nn",    "Nynorsk",               NO },
98    { "kk",    "Қазақ тілі",            NO },
99    { "km",    "ភាសាខ្មែរ",                NO },
100    { "ne",    "नेपाली",                  NO },
101    { "oc",    "Occitan",               NO },
102    { "pl",    "Polski",                NO },
103    { "pt_PT", "Português",             NO },
104    { "pa",    "ਪੰਜਾਬੀ",                  NO },
105    { "ro",    "Română",                NO },
106    { "ru",    "Русский",               NO },
107    { "zh_CN", "简体中文",                NO },
108    { "si",    "සිංහල",                   NO },
109    { "sr",    "српски",                NO },
110    { "sk",    "Slovensky",             NO },
111    { "sl",    "slovenščina",           NO },
112    { "es",    "Español",               NO },
113    { "es_MX", "Español Mexicano",      NO },
114    { "sv",    "Svenska",               NO },
115    { "th",    "ภาษาไทย",               NO },
116    { "tr",    "Türkçe",                NO },
117    { "uk",    "украї́нська мо́ва",       NO },
118    { "vi",    "tiếng Việt",            NO },
119    { "wa",    "Walon",                 NO }
120};
121
122static NSString* VLCSPrefsToolbarIdentifier = @"Our Simple Preferences Toolbar Identifier";
123static NSString* VLCIntfSettingToolbarIdentifier = @"Intf Settings Item Identifier";
124static NSString* VLCAudioSettingToolbarIdentifier = @"Audio Settings Item Identifier";
125static NSString* VLCVideoSettingToolbarIdentifier = @"Video Settings Item Identifier";
126static NSString* VLCOSDSettingToolbarIdentifier = @"Subtitles Settings Item Identifier";
127static NSString* VLCInputSettingToolbarIdentifier = @"Input Settings Item Identifier";
128static NSString* VLCHotkeysSettingToolbarIdentifier = @"Hotkeys Settings Item Identifier";
129
130@interface VLCSimplePrefsController() <NSToolbarDelegate, NSWindowDelegate>
131{
132    BOOL _audioSettingChanged;
133    BOOL _intfSettingChanged;
134    BOOL _videoSettingChanged;
135    BOOL _osdSettingChanged;
136    BOOL _inputSettingChanged;
137    BOOL _hotkeyChanged;
138
139    NSOpenPanel *_selectFolderPanel;
140    NSArray *_hotkeyDescriptions;
141    NSArray *_hotkeyNames;
142    NSArray *_hotkeysNonUseableKeys;
143    NSMutableArray *_hotkeySettings;
144    NSString *_keyInTransition;
145
146    intf_thread_t *p_intf;
147}
148@end
149
150@implementation VLCSimplePrefsController
151
152#pragma mark Initialisation
153
154- (id)init
155{
156    self = [super initWithWindowNibName:@"SimplePreferences"];
157    if (self) {
158        p_intf = getIntf();
159    }
160
161    return self;
162}
163
164- (void)windowDidLoad
165{
166    [self initStrings];
167
168#ifdef HAVE_SPARKLE
169    [_intf_updateCheckbox bind:@"value"
170                   toObject:[SUUpdater sharedUpdater]
171                withKeyPath:@"automaticallyChecksForUpdates"
172                    options:nil];
173#else
174    [_intf_updateCheckbox setState:NSOffState];
175    [_intf_updateCheckbox setEnabled:NO];
176#endif
177
178    /* setup the toolbar */
179    NSToolbar * toolbar = [[NSToolbar alloc] initWithIdentifier: VLCSPrefsToolbarIdentifier];
180    [toolbar setAllowsUserCustomization: NO];
181    [toolbar setAutosavesConfiguration: NO];
182    [toolbar setDisplayMode: NSToolbarDisplayModeIconAndLabel];
183    [toolbar setSizeMode: NSToolbarSizeModeRegular];
184    [toolbar setDelegate: self];
185    [self.window setToolbar:toolbar];
186
187    [self.window setCollectionBehavior: NSWindowCollectionBehaviorFullScreenAuxiliary];
188    [self.window setHidesOnDeactivate:YES];
189
190    [_hotkeys_listbox setTarget:self];
191    [_hotkeys_listbox setDoubleAction:@selector(hotkeyTableDoubleClick:)];
192
193    /* setup useful stuff */
194    _hotkeysNonUseableKeys = [NSArray arrayWithObjects:@"Command-c", @"Command-x", @"Command-v", @"Command-a", @"Command-," , @"Command-h", @"Command-Alt-h", @"Command-Shift-o", @"Command-o", @"Command-d", @"Command-n", @"Command-s", @"Command-l", @"Command-r", @"Command-3", @"Command-m", @"Command-w", @"Command-Shift-w", @"Command-Shift-c", @"Command-Shift-p", @"Command-i", @"Command-e", @"Command-Shift-e", @"Command-b", @"Command-Shift-m", @"Command-Ctrl-m", @"Command-?", @"Command-Alt-?", @"Command-Shift-f", nil];
195
196    // Workaround for Mac OS X Lion, which does not apply the same constraints when set in IB
197    NSView *clipView = _contentView.superview;
198
199    NSDictionary *views = @{ @"view": _contentView };
200    NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"|[view]|" options:0 metrics:nil views:views];
201    [clipView addConstraints:constraints];
202}
203
204#define CreateToolbarItem(name, desc, img, sel) \
205    toolbarItem = create_toolbar_item(itemIdent, name, desc, img, self, @selector(sel));
206static inline NSToolbarItem *
207create_toolbar_item(NSString *itemIdent, NSString *name, NSString *desc, NSString *img, id target, SEL selector)
208{
209    NSToolbarItem *toolbarItem = [[NSToolbarItem alloc] initWithItemIdentifier: itemIdent];
210
211    [toolbarItem setLabel:name];
212    [toolbarItem setPaletteLabel:desc];
213
214    [toolbarItem setToolTip:desc];
215    [toolbarItem setImage:[NSImage imageNamed:img]];
216
217    [toolbarItem setTarget:target];
218    [toolbarItem setAction:selector];
219
220    [toolbarItem setEnabled:YES];
221    [toolbarItem setAutovalidates:YES];
222
223    return toolbarItem;
224}
225
226- (NSToolbarItem *) toolbar:(NSToolbar *)toolbar
227      itemForItemIdentifier:(NSString *)itemIdent
228  willBeInsertedIntoToolbar:(BOOL)willBeInserted
229{
230    NSToolbarItem *toolbarItem = nil;
231
232    if ([itemIdent isEqual: VLCIntfSettingToolbarIdentifier]) {
233        CreateToolbarItem(_NS("Interface"), _NS("Interface Settings"), @"VLCInterfaceCone", showInterfaceSettings);
234    } else if ([itemIdent isEqual: VLCAudioSettingToolbarIdentifier]) {
235        CreateToolbarItem(_NS("Audio"), _NS("Audio Settings"), @"VLCAudioCone", showAudioSettings);
236    } else if ([itemIdent isEqual: VLCVideoSettingToolbarIdentifier]) {
237        CreateToolbarItem(_NS("Video"), _NS("Video Settings"), @"VLCVideoCone", showVideoSettings);
238    } else if ([itemIdent isEqual: VLCOSDSettingToolbarIdentifier]) {
239        CreateToolbarItem(_NS(SUBPIC_TITLE), _NS("Subtitle & On Screen Display Settings"), @"VLCSubtitleCone", showOSDSettings);
240    } else if ([itemIdent isEqual: VLCInputSettingToolbarIdentifier]) {
241        CreateToolbarItem(_NS(INPUT_TITLE), _NS("Input & Codec Settings"), @"VLCInputCone", showInputSettings);
242    } else if ([itemIdent isEqual: VLCHotkeysSettingToolbarIdentifier]) {
243        CreateToolbarItem(_NS("Hotkeys"), _NS("Hotkeys settings"), @"VLCHotkeysCone", showHotkeySettings);
244    }
245
246    return toolbarItem;
247}
248
249- (NSArray *)toolbarDefaultItemIdentifiers: (NSToolbar *)toolbar
250{
251    return [NSArray arrayWithObjects:VLCIntfSettingToolbarIdentifier, VLCAudioSettingToolbarIdentifier, VLCVideoSettingToolbarIdentifier,
252             VLCOSDSettingToolbarIdentifier, VLCInputSettingToolbarIdentifier, VLCHotkeysSettingToolbarIdentifier,
253             NSToolbarFlexibleSpaceItemIdentifier, nil];
254}
255
256- (NSArray *)toolbarAllowedItemIdentifiers: (NSToolbar *)toolbar
257{
258    return [NSArray arrayWithObjects:VLCIntfSettingToolbarIdentifier, VLCAudioSettingToolbarIdentifier, VLCVideoSettingToolbarIdentifier,
259             VLCOSDSettingToolbarIdentifier, VLCInputSettingToolbarIdentifier, VLCHotkeysSettingToolbarIdentifier,
260             NSToolbarFlexibleSpaceItemIdentifier, nil];
261}
262
263- (NSArray *)toolbarSelectableItemIdentifiers:(NSToolbar *)toolbar
264{
265    return [NSArray arrayWithObjects:VLCIntfSettingToolbarIdentifier, VLCAudioSettingToolbarIdentifier, VLCVideoSettingToolbarIdentifier,
266             VLCOSDSettingToolbarIdentifier, VLCInputSettingToolbarIdentifier, VLCHotkeysSettingToolbarIdentifier, nil];
267}
268
269- (void)initStrings
270{
271    /* audio */
272    [_audio_effectsBox setTitle: _NS("Audio Effects")];
273    [_audio_enableCheckbox setTitle: _NS("Enable audio")];
274    [_audio_generalBox setTitle: _NS("General Audio")];
275    [_audio_langLabel setStringValue: _NS("Preferred Audio language")];
276    [_audio_lastCheckbox setTitle: _NS("Enable Last.fm submissions")];
277    [_audio_lastpwdLabel setStringValue: _NS("Password")];
278    [_audio_lastuserLabel setStringValue: _NS("Username")];
279    [_audio_visualLabel setStringValue: _NS("Visualization")];
280    [_audio_autosavevol_yesButtonCell setTitle: _NS("Keep audio level between sessions")];
281    [_audio_autosavevol_noButtonCell setTitle: _NS("Always reset audio start level to:")];
282
283    /* hotkeys */
284    [_hotkeys_changeButton setTitle: _NS("Change")];
285    [_hotkeys_change_win setTitle: _NS("Change Hotkey")];
286    [_hotkeys_change_cancelButton setTitle: _NS("Cancel")];
287    [_hotkeys_change_okButton setTitle: _NS("OK")];
288    [_hotkeys_clearButton setTitle: _NS("Clear")];
289    [_hotkeysLabel setStringValue: _NS("Select an action to change the associated hotkey:")];
290    [[[_hotkeys_listbox tableColumnWithIdentifier: @"action"] headerCell] setStringValue: _NS("Action")];
291    [[[_hotkeys_listbox tableColumnWithIdentifier: @"shortcut"] headerCell] setStringValue: _NS("Shortcut")];
292
293    /* input */
294    [_input_recordBox setTitle: _NS("Record directory or filename")];
295    [_input_recordButton setTitle: _NS("Browse...")];
296    [_input_recordButton setToolTip: _NS("Directory or filename where the records will be stored")];
297    [_input_aviLabel setStringValue: _NS("Repair AVI Files")];
298    [_input_cachelevelLabel setStringValue: _NS("Default Caching Level")];
299    [_input_cachingBox setTitle: _NS("Caching")];
300    [_input_cachelevel_customLabel setStringValue: _NS("Use the complete preferences to configure custom caching values for each access module.")];
301    [_input_muxBox setTitle: _NS("Codecs / Muxers")];
302    [_input_netBox setTitle: _NS("Network")];
303    [_input_hardwareAccelerationCheckbox setTitle: _NS("Hardware decoding")];
304    [_input_postprocLabel setStringValue: _NS("Post-Processing Quality")];
305    [_input_skipLoopLabel setStringValue: _NS("Skip the loop filter for H.264 decoding")];
306    [_input_urlhandlerButton setTitle: _NS("Edit default application settings for network protocols")];
307    [_input_skipFramesCheckbox setTitle: _NS("Skip frames")];
308
309    /* url handler */
310    [_urlhandler_titleLabel setStringValue: _NS("Open network streams using the following protocols")];
311    [_urlhandler_subtitleLabel setStringValue: _NS("Note that these are system-wide settings.")];
312    [_urlhandler_saveButton setTitle: _NS("Save")];
313    [_urlhandler_cancelButton setTitle: _NS("Cancel")];
314
315    /* interface */
316    [_intf_generalSettingsBox setTitle:_NS("General settings")];
317    [_intf_languageLabel setStringValue: _NS("Language")];
318    [_intf_styleLabel setStringValue: _NS("Interface style")];
319    [_intf_style_darkButtonCell setTitle: _NS("Dark")];
320    [_intf_style_brightButtonCell setTitle: _NS("Bright")];
321
322    [_intf_playbackControlBox setTitle:_NS("Playback control")];
323    [_intf_continueplaybackLabel setStringValue:_NS("Continue playback")];
324    [_intf_appleremoteCheckbox setTitle: _NS("Control playback with the Apple Remote")];
325    [_intf_mediakeysCheckbox setTitle: _NS("Control playback with media keys")];
326    [_intf_appleremote_sysvolCheckbox setTitle: _NS("Control system volume with the Apple Remote")];
327    [_intf_statusIconCheckbox setTitle: _NS("Display VLC status menu icon")];
328
329    [_intf_playbackBehaviourBox setTitle:_NS("Playback behaviour")];
330    [_intf_enableNotificationsCheckbox setTitle: _NS("Enable notifications on playlist item change")];
331    [_intf_pauseitunesLabel setStringValue:_NS("Control external music players")];
332
333    [_intf_networkBox setTitle: _NS("Privacy / Network Interaction")];
334    [_intf_artCheckbox setTitle: _NS("Allow metadata network access")];
335    [_intf_updateCheckbox setTitle: _NS("Automatically check for updates")];
336    [_intf_last_updateLabel setStringValue: @""];
337
338    [_intf_luahttpBox setTitle:_NS("HTTP web interface")];
339    [_intf_enableluahttpCheckbox setTitle: _NS("Enable HTTP web interface")];
340    [_intf_luahttppwdLabel setStringValue:_NS("Password")];
341
342    /* Subtitles and OSD */
343    [_osd_encodingLabel setStringValue: _NS("Default Encoding")];
344    [_osd_fontBox setTitle: _NS("Display Settings")];
345    [_osd_fontButton setTitle: _NS("Choose...")];
346    [_osd_font_colorLabel setStringValue: _NS("Font color")];
347    [_osd_font_sizeLabel setStringValue: _NS("Font size")];
348    [_osd_fontLabel setStringValue: _NS("Font")];
349    [_osd_langBox setTitle: _NS("Subtitle languages")];
350    [_osd_langLabel setStringValue: _NS("Preferred subtitle language")];
351    [_osd_osdBox setTitle: _NS("On Screen Display")];
352    [_osd_osdCheckbox setTitle: _NS("Enable OSD")];
353    [_osd_opacityLabel setStringValue: _NS("Opacity")];
354    [_osd_forceboldCheckbox setTitle: _NS("Force bold")];
355    [_osd_outline_colorLabel setStringValue: _NS("Outline color")];
356    [_osd_outline_thicknessLabel setStringValue: _NS("Outline thickness")];
357
358    /* video */
359    [_video_enableCheckbox setTitle: _NS("Enable video")];
360    [_video_displayBox setTitle: _NS("Display")];
361    [_video_embeddedCheckbox setTitle: _NS("Show video within the main window")];
362    [_video_pauseWhenMinimizedCheckbox setTitle:_NS("Pause the video playback when minimized")];
363    [_video_onTopCheckbox setTitle: _NS("Float on Top")];
364    [_video_videodecoCheckbox setTitle: _NS("Window decorations")];
365
366    [_video_fullscreenBox setTitle:_NS("Fullscreen settings")];
367    [_video_startInFullscreenCheckbox setTitle:_NS("Start in fullscreen")];
368    [_video_blackScreenCheckbox setTitle: _NS("Black screens in Fullscreen mode")];
369    [_video_nativeFullscreenCheckbox setTitle: _NS("Use the native fullscreen mode")];
370    [_video_deviceLabel setStringValue: _NS("Fullscreen Video Device")];
371
372    [_video_snapBox setTitle: _NS("Video snapshots")];
373    [_video_snap_folderButton setTitle: _NS("Browse...")];
374    [_video_snap_folderLabel setStringValue: _NS("Folder")];
375    [_video_snap_formatLabel setStringValue: _NS("Format")];
376    [_video_snap_prefixLabel setStringValue: _NS("Prefix")];
377    [_video_snap_seqnumCheckbox setTitle: _NS("Sequential numbering")];
378    [_video_deinterlaceLabel setStringValue: _NS("Deinterlace")];
379    [_video_deinterlace_modeLabel setStringValue: _NS("Deinterlace mode")];
380    [_video_videoBox setTitle: _NS("Video")];
381
382    /* generic stuff */
383    [_showAllButton setTitle: _NS("Show All")];
384    [_cancelButton setTitle: _NS("Cancel")];
385    [_resetButton setTitle: _NS("Reset All")];
386    [_saveButton setTitle: _NS("Save")];
387    [self.window setTitle: _NS("Preferences")];
388}
389
390/* TODO: move this part to core */
391#define config_GetLabel(a,b) __config_GetLabel(VLC_OBJECT(a),b)
392static inline const char * __config_GetLabel(vlc_object_t *p_this, const char *psz_name)
393{
394    module_config_t *p_config = config_FindConfig(psz_name);
395
396    /* sanity checks */
397    if (!p_config) {
398        msg_Err(p_this, "option %s does not exist", psz_name);
399        return NULL;
400    }
401
402    if (p_config->psz_longtext)
403        return p_config->psz_longtext;
404    else if (p_config->psz_text)
405        return p_config->psz_text;
406    else
407        msg_Warn(p_this, "option %s does not include any help", psz_name);
408
409    return NULL;
410}
411
412#pragma mark -
413#pragma mark Setup controls
414
415- (void)setupButton: (NSPopUpButton *)object forStringList: (const char *)name
416{
417    module_config_t *p_item;
418
419    [object removeAllItems];
420    p_item = config_FindConfig(name);
421    /* serious problem, if no item found */
422    assert(p_item);
423
424    char **values, **texts;
425    ssize_t count = config_GetPszChoices(VLC_OBJECT(getIntf()), name,
426                                         &values, &texts);
427    if (count < 0) {
428        msg_Err(p_intf, "Cannot get choices for %s", name);
429        return;
430    }
431    for (ssize_t i = 0; i < count && texts; i++) {
432        if (texts[i] == NULL || values[i] == NULL)
433            continue;
434
435        if (strcmp(texts[i], "") != 0) {
436            NSMenuItem *mi = [[NSMenuItem alloc] initWithTitle: toNSStr(texts[i]) action: NULL keyEquivalent: @""];
437            [mi setRepresentedObject: toNSStr(values[i])];
438            [[object menu] addItem:mi];
439
440            if (p_item->value.psz && !strcmp(p_item->value.psz, values[i]))
441                [object selectItem: [object lastItem]];
442        } else {
443            [[object menu] addItem: [NSMenuItem separatorItem]];
444        }
445
446        free(texts[i]);
447        free(values[i]);
448    }
449
450    free(texts);
451    free(values);
452
453    if (p_item->psz_longtext)
454        [object setToolTip: _NS(p_item->psz_longtext)];
455}
456
457// just for clarification that this is a module list
458- (void)setupButton: (NSPopUpButton *)object forModuleList: (const char *)name
459{
460    [self setupButton: object forStringList: name];
461}
462
463- (void)setupButton: (NSPopUpButton *)object forIntList: (const char *)name
464{
465    module_config_t *p_item;
466
467    [object removeAllItems];
468    p_item = config_FindConfig(name);
469
470    /* serious problem, if no item found */
471    assert(p_item);
472
473    int64_t *values;
474    char **texts;
475    ssize_t count = config_GetIntChoices(VLC_OBJECT(getIntf()), name, &values, &texts);
476    for (ssize_t i = 0; i < count; i++) {
477        NSMenuItem *mi = [[NSMenuItem alloc] initWithTitle: toNSStr(texts[i]) action: NULL keyEquivalent: @""];
478        [mi setRepresentedObject:[NSNumber numberWithInt:values[i]]];
479        [[object menu] addItem:mi];
480
481        if (p_item->value.i == values[i])
482            [object selectItem:[object lastItem]];
483
484        free(texts[i]);
485    }
486    free(texts);
487
488    if (p_item->psz_longtext)
489        [object setToolTip: _NS(p_item->psz_longtext)];
490}
491
492- (void)setupButton: (NSButton *)object forBoolValue: (const char *)name
493{
494    [object setState: config_GetInt(p_intf, name)];
495    [object setToolTip: _NS(config_GetLabel(p_intf, name))];
496}
497
498- (void)setupField:(NSTextField *)object forOption:(const char *)psz_option
499{
500    char *psz_tmp = config_GetPsz(p_intf, psz_option);
501    [object setStringValue: toNSStr(psz_tmp)];
502    [object setToolTip: _NS(config_GetLabel(p_intf, psz_option))];
503    free(psz_tmp);
504}
505
506- (BOOL)hasModule:(NSString *)moduleName inConfig:(NSString *)config
507{
508    char *value = config_GetPsz(p_intf, [config UTF8String]);
509    NSString *modules = toNSStr(value);
510    free(value);
511
512    return [[modules componentsSeparatedByString:@":"] containsObject:moduleName];
513}
514
515- (void)changeModule:(NSString *)moduleName inConfig:(NSString *)config enable:(BOOL)enable
516{
517    char *value = config_GetPsz(p_intf, [config UTF8String]);
518    NSString *modules = toNSStr(value);
519    free(value);
520
521    NSMutableArray *components = [[modules componentsSeparatedByString:@":"] mutableCopy];
522    if (enable) {
523        if (![components containsObject:moduleName]) {
524            [components addObject:moduleName];
525        }
526    } else {
527        [components removeObject:moduleName];
528    }
529
530    // trim empty entries
531    [components removeObject:@""];
532
533    config_PutPsz(p_intf, [config UTF8String], [[components componentsJoinedByString:@":"] UTF8String]);
534}
535
536- (void)resetControls
537{
538    module_config_t *p_item;
539    int i, y = 0;
540    char *psz_tmp;
541
542    /**********************
543     * interface settings *
544     **********************/
545    NSUInteger sel = 0;
546    const char *pref = NULL;
547    pref = [[[NSUserDefaults standardUserDefaults] objectForKey:@"language"] UTF8String];
548    for (int x = 0; x < ARRAY_SIZE(language_map); x++) {
549        [_intf_languagePopup addItemWithTitle:toNSStr(language_map[x].name)];
550        if (pref) {
551            if (!strcmp(language_map[x].iso, pref))
552                sel = x;
553        }
554    }
555    [_intf_languagePopup selectItemAtIndex:sel];
556
557    [self setupButton:_intf_continueplaybackPopup forIntList: "macosx-continue-playback"];
558    if (!var_InheritBool(p_intf, "macosx-recentitems")) {
559        [_intf_continueplaybackPopup setEnabled: NO];
560        [_intf_continueplaybackPopup setToolTip: _NS("Media files cannot be resumed because keeping recent media items is disabled.")];
561    } else {
562        [_intf_continueplaybackPopup setEnabled: YES];
563    }
564
565    [self setupButton:_intf_appleremoteCheckbox forBoolValue: "macosx-appleremote"];
566    [self setupButton:_intf_appleremote_sysvolCheckbox forBoolValue: "macosx-appleremote-sysvol"];
567    [self setupButton:_intf_statusIconCheckbox forBoolValue: "macosx-statusicon"];
568    if (OSX_SIERRA_AND_HIGHER) {
569        _intf_mediakeysCheckbox.enabled = NO;
570        _intf_mediakeysCheckbox.intValue = YES;
571    } else {
572        [self setupButton:_intf_mediakeysCheckbox forBoolValue: "macosx-mediakeys"];
573    }
574
575    [self setupButton:_video_nativeFullscreenCheckbox forBoolValue: "macosx-nativefullscreenmode"];
576    [self setupButton:_video_embeddedCheckbox forBoolValue: "embedded-video"];
577
578    [self setupButton:_intf_pauseitunesPopup forIntList: "macosx-control-itunes"];
579
580    [self setupButton:_intf_artCheckbox forBoolValue: "metadata-network-access"];
581
582
583#ifdef HAVE_SPARKLE
584    if ([[SUUpdater sharedUpdater] lastUpdateCheckDate] != NULL)
585        [_intf_last_updateLabel setStringValue: [NSString stringWithFormat: _NS("Last check on: %@"), [[[SUUpdater sharedUpdater] lastUpdateCheckDate] descriptionWithLocale: [[NSUserDefaults standardUserDefaults] dictionaryRepresentation]]]];
586    else
587        [_intf_last_updateLabel setStringValue: _NS("No check was performed yet.")];
588#endif
589
590    BOOL growlEnabled = [self hasModule:@"growl" inConfig:@"control"];
591    [_intf_enableNotificationsCheckbox setState: growlEnabled ? NSOnState : NSOffState];
592
593    if (config_GetInt(p_intf, "macosx-interfacestyle")) {
594        [_intf_style_darkButtonCell setState: YES];
595        [_intf_style_brightButtonCell setState: NO];
596    } else {
597        [_intf_style_darkButtonCell setState: NO];
598        [_intf_style_brightButtonCell setState: YES];
599    }
600
601    BOOL httpEnabled = [self hasModule:@"http" inConfig:@"extraintf"];
602    [_intf_enableluahttpCheckbox setState: httpEnabled ? NSOnState : NSOffState];
603    _intf_luahttppwdTextField.enabled = httpEnabled;
604
605    [self setupField:_intf_luahttppwdTextField forOption: "http-password"];
606
607    /******************
608     * audio settings *
609     ******************/
610    [self setupButton:_audio_enableCheckbox forBoolValue: "audio"];
611
612    if (config_GetInt(p_intf, "volume-save")) {
613        [_audio_autosavevol_yesButtonCell setState: NSOnState];
614        [_audio_autosavevol_noButtonCell setState: NSOffState];
615        [_audio_volTextField setEnabled: NO];
616        [_audio_volSlider setEnabled: NO];
617
618        [_audio_volSlider setIntValue: 100];
619        [_audio_volTextField setIntValue: 100];
620    } else {
621        [_audio_autosavevol_yesButtonCell setState: NSOffState];
622        [_audio_autosavevol_noButtonCell setState: NSOnState];
623        [_audio_volTextField setEnabled: YES];
624        [_audio_volSlider setEnabled: YES];
625
626        i = var_InheritInteger(p_intf, "auhal-volume");
627        i = i * 200. / AOUT_VOLUME_MAX;
628        [_audio_volSlider setIntValue: i];
629        [_audio_volTextField setIntValue: i];
630    }
631
632    [self setupField:_audio_langTextField forOption: "audio-language"];
633
634    [self setupButton:_audio_visualPopup forModuleList: "audio-visual"];
635
636    /* Last.FM is optional */
637    if (module_exists("audioscrobbler")) {
638        [self setupField:_audio_lastuserTextField forOption:"lastfm-username"];
639        [self setupField:_audio_lastpwdSecureTextField forOption:"lastfm-password"];
640
641        if (config_ExistIntf(VLC_OBJECT(p_intf), "audioscrobbler")) {
642            [_audio_lastCheckbox setState: NSOnState];
643            [_audio_lastuserTextField setEnabled: YES];
644            [_audio_lastpwdSecureTextField setEnabled: YES];
645        } else {
646            [_audio_lastCheckbox setState: NSOffState];
647            [_audio_lastuserTextField setEnabled: NO];
648            [_audio_lastpwdSecureTextField setEnabled: NO];
649        }
650    } else
651        [_audio_lastCheckbox setEnabled: NO];
652
653    /******************
654     * video settings *
655     ******************/
656    [self setupButton:_video_enableCheckbox forBoolValue: "video"];
657    [self setupButton:_video_startInFullscreenCheckbox forBoolValue: "fullscreen"];
658    [self setupButton:_video_onTopCheckbox forBoolValue: "video-on-top"];
659    [self setupButton:_video_blackScreenCheckbox forBoolValue: "macosx-black"];
660    [self setupButton:_video_videodecoCheckbox forBoolValue: "video-deco"];
661    [self setupButton:_video_pauseWhenMinimizedCheckbox forBoolValue: "macosx-pause-minimized"];
662
663    [_video_devicePopup removeAllItems];
664    i = 0;
665    y = [[NSScreen screens] count];
666    [_video_devicePopup addItemWithTitle: _NS("Default")];
667    [[_video_devicePopup lastItem] setTag: 0];
668    while (i < y) {
669        NSRect s_rect = [[[NSScreen screens] objectAtIndex:i] frame];
670        [_video_devicePopup addItemWithTitle:
671         [NSString stringWithFormat: @"%@ %i (%ix%i)", _NS("Screen"), i+1,
672                   (int)s_rect.size.width, (int)s_rect.size.height]];
673        [[_video_devicePopup lastItem] setTag: (int)[[[NSScreen screens] objectAtIndex:i] displayID]];
674        i++;
675    }
676    [_video_devicePopup selectItemAtIndex: 0];
677    [_video_devicePopup selectItemWithTag: config_GetInt(p_intf, "macosx-vdev")];
678
679    [self setupField:_video_snap_folderTextField forOption:"snapshot-path"];
680    [self setupField:_video_snap_prefixTextField forOption:"snapshot-prefix"];
681    [self setupButton:_video_snap_seqnumCheckbox forBoolValue: "snapshot-sequential"];
682    [self setupButton:_video_snap_formatPopup forStringList: "snapshot-format"];
683    [self setupButton:_video_deinterlacePopup forIntList: "deinterlace"];
684    [self setupButton:_video_deinterlace_modePopup forStringList: "deinterlace-mode"];
685
686    // set lion fullscreen mode restrictions
687    [self enableLionFullscreenMode: [_video_nativeFullscreenCheckbox state]];
688
689    /***************************
690     * input & codecs settings *
691     ***************************/
692    [self setupField:_input_recordTextField forOption:"input-record-path"];
693
694    [self setupButton:_input_hardwareAccelerationCheckbox forBoolValue: "videotoolbox"];
695    [_input_postprocTextField setIntValue: config_GetInt(p_intf, "postproc-q")];
696    [_input_postprocTextField setToolTip: _NS(config_GetLabel(p_intf, "postproc-q"))];
697    [self setupButton:_input_skipFramesCheckbox forBoolValue: "skip-frames"];
698    [self setupButton:_input_aviPopup forIntList: "avi-index"];
699    [self setupButton:_input_skipLoopPopup forIntList: "avcodec-skiploopfilter"];
700
701    [_input_cachelevelPopup removeAllItems];
702    NSMenuItem *item = [[_input_cachelevelPopup menu] addItemWithTitle:_NS("Custom") action:nil keyEquivalent:@""];
703    [item setTag: 0];
704    item = [[_input_cachelevelPopup menu] addItemWithTitle:_NS("Lowest Latency") action:nil keyEquivalent:@""];
705    [item setTag: 100];
706    item = [[_input_cachelevelPopup menu] addItemWithTitle:_NS("Low Latency") action:nil keyEquivalent:@""];
707    [item setTag: 200];
708    item = [[_input_cachelevelPopup menu] addItemWithTitle:_NS("Normal") action:nil keyEquivalent:@""];
709    [item setTag: 300];
710    item = [[_input_cachelevelPopup menu] addItemWithTitle:_NS("Higher Latency") action:nil keyEquivalent:@""];
711    [item setTag: 500];
712    item = [[_input_cachelevelPopup menu] addItemWithTitle:_NS("Highest Latency") action:nil keyEquivalent:@""];
713    [item setTag: 1000];
714
715    #define TestCaC(name, factor) \
716    cache_equal = cache_equal && \
717    (i_cache * factor == config_GetInt(p_intf, name));
718
719    /* Select the accurate value of the PopupButton */
720    bool cache_equal = true;
721    int i_cache = config_GetInt(p_intf, "file-caching");
722
723    TestCaC("network-caching", 10/3);
724    TestCaC("disc-caching", 1);
725    TestCaC("live-caching", 1);
726    if (cache_equal) {
727        [_input_cachelevelPopup selectItemWithTag: i_cache];
728        [_input_cachelevel_customLabel setHidden: YES];
729    } else {
730        [_input_cachelevelPopup selectItemWithTitle: _NS("Custom")];
731        [_input_cachelevel_customLabel setHidden: NO];
732    }
733    #undef TestCaC
734
735    /*********************
736     * subtitle settings *
737     *********************/
738    [self setupButton:_osd_osdCheckbox forBoolValue: "osd"];
739
740    [self setupButton:_osd_encodingPopup forStringList: "subsdec-encoding"];
741    [self setupField:_osd_langTextField forOption: "sub-language" ];
742
743    [self setupField:_osd_fontTextField forOption: "freetype-font"];
744    [self setupButton:_osd_font_colorPopup forIntList: "freetype-color"];
745    [self setupButton:_osd_font_sizePopup forIntList: "freetype-rel-fontsize"];
746    i = config_GetInt(p_intf, "freetype-opacity") * 100.0 / 255.0 + 0.5;
747    [_osd_opacityTextField setIntValue: i];
748    [_osd_opacitySlider setIntValue: i];
749    [_osd_opacitySlider setToolTip: _NS(config_GetLabel(p_intf, "freetype-opacity"))];
750    [_osd_opacityTextField setToolTip: [_osd_opacitySlider toolTip]];
751    [self setupButton:_osd_forceboldCheckbox forBoolValue: "freetype-bold"];
752    [self setupButton:_osd_outline_colorPopup forIntList: "freetype-outline-color"];
753    [self setupButton:_osd_outline_thicknessPopup forIntList: "freetype-outline-thickness"];
754
755    /********************
756     * hotkeys settings *
757     ********************/
758    _hotkeySettings = [[NSMutableArray alloc] init];
759    NSMutableArray *tempArray_desc = [[NSMutableArray alloc] init];
760    NSMutableArray *tempArray_names = [[NSMutableArray alloc] init];
761
762    /* Get the main Module */
763    module_t *p_main = module_get_main();
764    assert(p_main);
765    unsigned confsize;
766    module_config_t *p_config;
767
768    p_config = module_config_get (p_main, &confsize);
769
770    for (size_t i = 0; i < confsize; i++) {
771        module_config_t *p_item = p_config + i;
772
773        if (CONFIG_ITEM(p_item->i_type) && p_item->psz_name != NULL
774           && !strncmp(p_item->psz_name , "key-", 4)
775           && !EMPTY_STR(p_item->psz_text)) {
776            [tempArray_desc addObject: _NS(p_item->psz_text)];
777            [tempArray_names addObject: toNSStr(p_item->psz_name)];
778            if (p_item->value.psz)
779                [_hotkeySettings addObject: toNSStr(p_item->value.psz)];
780            else
781                [_hotkeySettings addObject: [NSString string]];
782        }
783    }
784    module_config_free (p_config);
785
786    _hotkeyDescriptions = [[NSArray alloc] initWithArray:tempArray_desc copyItems: YES];
787    _hotkeyNames = [[NSArray alloc] initWithArray:tempArray_names copyItems: YES];
788
789    [_hotkeys_listbox reloadData];
790}
791
792#pragma mark -
793#pragma mark General actions
794
795- (void)showSimplePrefs
796{
797    /* we want to show the interface settings, if no category was chosen */
798    if ([[self.window toolbar] selectedItemIdentifier] == nil) {
799        [[self.window toolbar] setSelectedItemIdentifier: VLCIntfSettingToolbarIdentifier];
800        [self showInterfaceSettings];
801    }
802
803    [self resetControls];
804
805    [self.window makeKeyAndOrderFront: self];
806}
807
808- (void)showSimplePrefsWithLevel:(NSInteger)i_window_level
809{
810    [self.window setLevel: i_window_level];
811    [self showSimplePrefs];
812}
813
814- (IBAction)buttonAction:(id)sender
815{
816    if (sender == _cancelButton) {
817        [[NSFontPanel sharedFontPanel] close];
818        [self.window orderOut: sender];
819    } else if (sender == _saveButton) {
820        [self saveChangedSettings];
821        [[NSFontPanel sharedFontPanel] close];
822        [self.window orderOut: sender];
823    } else if (sender == _showAllButton) {
824        [self.window orderOut: self];
825        [[[VLCMain sharedInstance] preferences] showPrefsWithLevel:[self.window level]];
826    } else
827        msg_Warn(p_intf, "unknown buttonAction sender");
828}
829
830- (IBAction)resetPreferences:(NSControl *)sender
831{
832    NSBeginInformationalAlertSheet(_NS("Reset Preferences"), _NS("Cancel"),
833                                   _NS("Continue"), nil, [sender window], self,
834                                   @selector(sheetDidEnd: returnCode: contextInfo:), NULL, nil, @"%@",
835                                   _NS("This will reset VLC media player's preferences.\n\n"
836                                       "Note that VLC will restart during the process, so your current "
837                                       "playlist will be emptied and eventual playback, streaming or "
838                                       "transcoding activities will stop immediately.\n\n"
839                                       "The Media Library will not be affected.\n\n"
840                                       "Are you sure you want to continue?"));
841}
842
843- (void)sheetDidEnd:(NSWindow *)o_sheet
844         returnCode:(int)i_return
845        contextInfo:(void *)o_context
846{
847    if (i_return == NSAlertAlternateReturn) {
848        /* reset VLC's config */
849        config_ResetAll(p_intf);
850        [self resetControls];
851
852        /* force config file creation, since libvlc won't exit normally */
853        config_SaveConfigFile(p_intf);
854
855        /* reset OS X defaults */
856        [[VLCMain sharedInstance] resetAndReinitializeUserDefaults];
857
858        /* Relaunch now */
859        const char * path = [[[NSBundle mainBundle] executablePath] UTF8String];
860
861        /* For some reason we need to fork(), not just execl(), which reports a ENOTSUP then. */
862        if (fork() != 0) {
863            exit(0);
864            return;
865        }
866        execl(path, path, NULL);
867    }
868}
869
870static inline void save_int_list(intf_thread_t * p_intf, id object, const char * name)
871{
872    NSNumber *p_valueobject = (NSNumber *)[[object selectedItem] representedObject];
873    if (p_valueobject) {
874        assert([p_valueobject isKindOfClass:[NSNumber class]]);
875        config_PutInt(p_intf, name, [p_valueobject intValue]);
876    }
877}
878
879static inline void save_string_list(intf_thread_t * p_intf, id object, const char * name)
880{
881    NSString *p_stringobject = (NSString *)[[object selectedItem] representedObject];
882    if (p_stringobject) {
883        assert([p_stringobject isKindOfClass:[NSString class]]);
884        config_PutPsz(p_intf, name, [p_stringobject UTF8String]);
885    }
886}
887
888+ (BOOL)updateRightToLeftSettings
889{
890    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
891    NSString *isoCode = [defaults stringForKey:@"language"];
892
893    if (!isoCode || [isoCode isEqualToString:@"auto"]) {
894        // Automatic handling of right to left
895        [defaults removeObjectForKey:@"NSForceRightToLeftWritingDirection"];
896        [defaults removeObjectForKey:@"AppleTextDirection"];
897    } else {
898        for(int i = 0; i < ARRAY_SIZE(language_map); i++) {
899            if (!strcmp(language_map[i].iso, [isoCode UTF8String])) {
900                [defaults setBool:language_map[i].isRightToLeft forKey:@"NSForceRightToLeftWritingDirection"];
901                [defaults setBool:language_map[i].isRightToLeft forKey:@"AppleTextDirection"];
902                return YES;
903            }
904        }
905    }
906
907    return NO;
908}
909
910- (void)saveChangedSettings
911{
912#define SaveIntList(object, name) save_int_list(p_intf, object, name)
913
914#define SaveStringList(object, name) save_string_list(p_intf, object, name)
915#define SaveModuleList(object, name) SaveStringList(object, name)
916
917    /**********************
918     * interface settings *
919     **********************/
920    if (_intfSettingChanged) {
921        NSUInteger index = [_intf_languagePopup indexOfSelectedItem];
922        NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
923        [defaults setObject:toNSStr(language_map[index].iso) forKey:@"language"];
924        [VLCSimplePrefsController updateRightToLeftSettings];
925        [defaults synchronize];
926
927        config_PutInt(p_intf, "metadata-network-access", [_intf_artCheckbox state]);
928
929        config_PutInt(p_intf, "macosx-appleremote", [_intf_appleremoteCheckbox state]);
930        config_PutInt(p_intf, "macosx-appleremote-sysvol", [_intf_appleremote_sysvolCheckbox state]);
931        config_PutInt(p_intf, "macosx-statusicon", [_intf_statusIconCheckbox state]);
932        config_PutInt(p_intf, "macosx-mediakeys", [_intf_mediakeysCheckbox state]);
933        config_PutInt(p_intf, "macosx-interfacestyle", [_intf_style_darkButtonCell state]);
934
935        [self changeModule:@"growl" inConfig:@"control" enable:[_intf_enableNotificationsCheckbox state] == NSOnState];
936
937        [self changeModule:@"http" inConfig:@"extraintf" enable:[_intf_enableluahttpCheckbox state] == NSOnState];
938        config_PutPsz(p_intf, "http-password", [[_intf_luahttppwdTextField stringValue] UTF8String]);
939
940        SaveIntList(_intf_pauseitunesPopup, "macosx-control-itunes");
941        SaveIntList(_intf_continueplaybackPopup, "macosx-continue-playback");
942
943        /* activate stuff without restart */
944        if ([_intf_appleremoteCheckbox state] == YES)
945            [[VLCCoreInteraction sharedInstance] startListeningWithAppleRemote];
946        else
947            [[VLCCoreInteraction sharedInstance] stopListeningWithAppleRemote];
948        _intfSettingChanged = NO;
949    }
950
951    /******************
952     * audio settings *
953     ******************/
954    if (_audioSettingChanged) {
955        config_PutInt(p_intf, "audio", [_audio_enableCheckbox state]);
956        config_PutInt(p_intf, "volume-save", [_audio_autosavevol_yesButtonCell state]);
957        var_SetBool(p_intf, "volume-save", [_audio_autosavevol_yesButtonCell state]);
958        if ([_audio_volTextField isEnabled])
959            config_PutInt(p_intf, "auhal-volume", ([_audio_volTextField intValue] * AOUT_VOLUME_MAX) / 200);
960
961        config_PutPsz(p_intf, "audio-language", [[_audio_langTextField stringValue] UTF8String]);
962
963        SaveModuleList(_audio_visualPopup, "audio-visual");
964
965        /* Last.FM is optional */
966        if (module_exists("audioscrobbler")) {
967            [_audio_lastCheckbox setEnabled: YES];
968            if ([_audio_lastCheckbox state] == NSOnState)
969                config_AddIntf(p_intf, "audioscrobbler");
970            else
971                config_RemoveIntf(p_intf, "audioscrobbler");
972
973            config_PutPsz(p_intf, "lastfm-username", [[_audio_lastuserTextField stringValue] UTF8String]);
974            config_PutPsz(p_intf, "lastfm-password", [[_audio_lastpwdSecureTextField stringValue] UTF8String]);
975        }
976        else
977            [_audio_lastCheckbox setEnabled: NO];
978        _audioSettingChanged = NO;
979    }
980
981    /******************
982     * video settings *
983     ******************/
984    if (_videoSettingChanged) {
985        config_PutInt(p_intf, "video", [_video_enableCheckbox state]);
986        config_PutInt(p_intf, "fullscreen", [_video_startInFullscreenCheckbox state]);
987        config_PutInt(p_intf, "video-deco", [_video_videodecoCheckbox state]);
988        config_PutInt(p_intf, "video-on-top", [_video_onTopCheckbox state]);
989        config_PutInt(p_intf, "macosx-black", [_video_blackScreenCheckbox state]);
990
991        config_PutInt(p_intf, "macosx-pause-minimized", [_video_pauseWhenMinimizedCheckbox state]);
992
993        config_PutInt(p_intf, "embedded-video", [_video_embeddedCheckbox state]);
994        config_PutInt(p_intf, "macosx-nativefullscreenmode", [_video_nativeFullscreenCheckbox state]);
995        config_PutInt(p_intf, "macosx-vdev", [[_video_devicePopup selectedItem] tag]);
996
997        config_PutPsz(p_intf, "snapshot-path", [[_video_snap_folderTextField stringValue] UTF8String]);
998        config_PutPsz(p_intf, "snapshot-prefix", [[_video_snap_prefixTextField stringValue] UTF8String]);
999        config_PutInt(p_intf, "snapshot-sequential", [_video_snap_seqnumCheckbox state]);
1000        SaveStringList(_video_snap_formatPopup, "snapshot-format");
1001        SaveIntList(_video_deinterlacePopup, "deinterlace");
1002        SaveStringList(_video_deinterlace_modePopup, "deinterlace-mode");
1003        _videoSettingChanged = NO;
1004    }
1005
1006    /***************************
1007     * input & codecs settings *
1008     ***************************/
1009    if (_inputSettingChanged) {
1010        config_PutPsz(p_intf, "input-record-path", [[_input_recordTextField stringValue] UTF8String]);
1011
1012        config_PutInt(p_intf, "videotoolbox", [_input_hardwareAccelerationCheckbox state]);
1013        config_PutInt(p_intf, "postproc-q", [_input_postprocTextField intValue]);
1014        config_PutInt(p_intf, "skip-frames", [_input_skipFramesCheckbox state]);
1015
1016        SaveIntList(_input_aviPopup, "avi-index");
1017
1018        SaveIntList(_input_skipLoopPopup, "avcodec-skiploopfilter");
1019
1020        #define CaC(name, factor) config_PutInt(p_intf, name, [[_input_cachelevelPopup selectedItem] tag] * factor)
1021        if ([[_input_cachelevelPopup selectedItem] tag] == 0) {
1022            msg_Dbg(p_intf, "Custom chosen, not adjusting cache values");
1023        } else {
1024            msg_Dbg(p_intf, "Adjusting all cache values to: %i", (int)[[_input_cachelevelPopup selectedItem] tag]);
1025            CaC("file-caching", 1);
1026            CaC("network-caching", 10/3);
1027            CaC("disc-caching", 1);
1028            CaC("live-caching", 1);
1029        }
1030        #undef CaC
1031        _inputSettingChanged = NO;
1032    }
1033
1034    /**********************
1035     * subtitles settings *
1036     **********************/
1037    if (_osdSettingChanged) {
1038        config_PutInt(p_intf, "osd", [_osd_osdCheckbox state]);
1039
1040        if ([_osd_encodingPopup indexOfSelectedItem] >= 0)
1041            SaveStringList(_osd_encodingPopup, "subsdec-encoding");
1042        else
1043            config_PutPsz(p_intf, "subsdec-encoding", "");
1044
1045        config_PutPsz(p_intf, "sub-language", [[_osd_langTextField stringValue] UTF8String]);
1046
1047        config_PutPsz(p_intf, "freetype-font", [[_osd_fontTextField stringValue] UTF8String]);
1048        SaveIntList(_osd_font_colorPopup, "freetype-color");
1049        SaveIntList(_osd_font_sizePopup, "freetype-rel-fontsize");
1050        config_PutInt(p_intf, "freetype-opacity", [_osd_opacityTextField intValue] * 255.0 / 100.0 + 0.5);
1051        config_PutInt(p_intf, "freetype-bold", [_osd_forceboldCheckbox state]);
1052        SaveIntList(_osd_outline_colorPopup, "freetype-outline-color");
1053        SaveIntList(_osd_outline_thicknessPopup, "freetype-outline-thickness");
1054        _osdSettingChanged = NO;
1055    }
1056
1057    /********************
1058     * hotkeys settings *
1059     ********************/
1060    if (_hotkeyChanged) {
1061        NSUInteger hotKeyCount = [_hotkeySettings count];
1062        for (NSUInteger i = 0; i < hotKeyCount; i++)
1063            config_PutPsz(p_intf, [[_hotkeyNames objectAtIndex:i] UTF8String], [[_hotkeySettings objectAtIndex:i]UTF8String]);
1064        _hotkeyChanged = NO;
1065    }
1066
1067    [[VLCCoreInteraction sharedInstance] fixIntfSettings];
1068
1069    /* okay, let's save our changes to vlcrc */
1070    config_SaveConfigFile(p_intf);
1071
1072    [[NSNotificationCenter defaultCenter] postNotificationName:VLCMediaKeySupportSettingChangedNotification object:nil];
1073    [[NSNotificationCenter defaultCenter] postNotificationName:VLCConfigurationChangedNotification object:nil];
1074}
1075
1076- (void)showSettingsForCategory:(NSView *)categoryView
1077{
1078    [_contentView setSubviews:[NSArray array]];
1079    [_contentView addSubview:categoryView];
1080
1081    NSDictionary *views = @{ @"view": categoryView };
1082    NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"|[view]|" options:0 metrics:nil views:views];
1083    [_contentView addConstraints:constraints];
1084    constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[view]|" options:0 metrics:nil views:views];
1085    [_contentView addConstraints:constraints];
1086
1087    [_scrollView layoutSubtreeIfNeeded];
1088    [_scrollView flashScrollers];
1089}
1090
1091#pragma mark -
1092#pragma mark Specific actions
1093
1094// disables some video settings which do not work in lion mode
1095- (void)enableLionFullscreenMode: (BOOL)_value
1096{
1097    [_video_videodecoCheckbox setEnabled: !_value];
1098    [_video_blackScreenCheckbox setEnabled: !_value];
1099    [_video_devicePopup setEnabled: !_value];
1100
1101    if (_value) {
1102        [_video_videodecoCheckbox setState: NSOnState];
1103        [_video_blackScreenCheckbox setState: NSOffState];
1104
1105        NSString *tooltipText = _NS("This setting cannot be changed because the native fullscreen mode is enabled.");
1106        [_video_videodecoCheckbox setToolTip:tooltipText];
1107        [_video_blackScreenCheckbox setToolTip:tooltipText];
1108        [_video_devicePopup setToolTip:tooltipText];
1109
1110    } else {
1111        [self setupButton:_video_videodecoCheckbox forBoolValue: "video-deco"];
1112        [self setupButton:_video_blackScreenCheckbox forBoolValue: "macosx-black"];
1113
1114        [_video_devicePopup setToolTip:@""];
1115    }
1116}
1117
1118- (IBAction)interfaceSettingChanged:(id)sender
1119{
1120    if (sender == _intf_enableluahttpCheckbox) {
1121        _intf_luahttppwdTextField.enabled = [sender state] == NSOnState;
1122    }
1123
1124    _intfSettingChanged = YES;
1125}
1126
1127- (void)showInterfaceSettings
1128{
1129    [self showSettingsForCategory:_intfView];
1130}
1131
1132- (IBAction)audioSettingChanged:(id)sender
1133{
1134    if (sender == _audio_volSlider)
1135        [_audio_volTextField setIntValue: [_audio_volSlider intValue]];
1136
1137    if (sender == _audio_volTextField)
1138        [_audio_volSlider setIntValue: [_audio_volTextField intValue]];
1139
1140    if (sender == _audio_lastCheckbox) {
1141        if ([_audio_lastCheckbox state] == NSOnState) {
1142            [_audio_lastpwdSecureTextField setEnabled: YES];
1143            [_audio_lastuserTextField setEnabled: YES];
1144        } else {
1145            [_audio_lastpwdSecureTextField setEnabled: NO];
1146            [_audio_lastuserTextField setEnabled: NO];
1147        }
1148    }
1149
1150    if (sender == _audio_autosavevolMatrix) {
1151        BOOL enableVolumeSlider = [_audio_autosavevolMatrix selectedTag] == 1;
1152        [_audio_volTextField setEnabled: enableVolumeSlider];
1153        [_audio_volSlider setEnabled: enableVolumeSlider];
1154    }
1155
1156    _audioSettingChanged = YES;
1157}
1158
1159- (void)showAudioSettings
1160{
1161    [self showSettingsForCategory:_audioView];
1162}
1163
1164- (IBAction)videoSettingChanged:(id)sender
1165{
1166    if (sender == _video_nativeFullscreenCheckbox)
1167        [self enableLionFullscreenMode:[sender state]];
1168    else if (sender == _video_snap_folderButton) {
1169        _selectFolderPanel = [[NSOpenPanel alloc] init];
1170        [_selectFolderPanel setCanChooseDirectories: YES];
1171        [_selectFolderPanel setCanChooseFiles: NO];
1172        [_selectFolderPanel setResolvesAliases: YES];
1173        [_selectFolderPanel setAllowsMultipleSelection: NO];
1174        [_selectFolderPanel setMessage: _NS("Choose the folder to save your video snapshots to.")];
1175        [_selectFolderPanel setCanCreateDirectories: YES];
1176        [_selectFolderPanel setPrompt: _NS("Choose")];
1177        [_selectFolderPanel beginSheetModalForWindow:self.window completionHandler: ^(NSInteger returnCode) {
1178            if (returnCode == NSOKButton) {
1179                [_video_snap_folderTextField setStringValue: [[_selectFolderPanel URL] path]];
1180            }
1181        }];
1182    }
1183
1184    _videoSettingChanged = YES;
1185}
1186
1187- (void)showVideoSettings
1188{
1189    [self showSettingsForCategory:_videoView];
1190}
1191
1192- (IBAction)osdSettingChanged:(id)sender
1193{
1194    if (sender == _osd_opacityTextField)
1195        [_osd_opacitySlider setIntValue: [_osd_opacityTextField intValue]];
1196
1197    if (sender == _osd_opacitySlider)
1198        [_osd_opacityTextField setIntValue: [_osd_opacitySlider intValue]];
1199
1200    _osdSettingChanged = YES;
1201}
1202
1203- (void)showOSDSettings
1204{
1205    [self showSettingsForCategory:_osdView];
1206}
1207
1208- (void)controlTextDidChange:(NSNotification *)notification
1209{
1210    id notificationObject = [notification object];
1211    if (notificationObject == _audio_langTextField ||
1212       notificationObject ==  _audio_lastpwdSecureTextField ||
1213       notificationObject ==  _audio_lastuserTextField ||
1214       notificationObject == _audio_volTextField)
1215        _audioSettingChanged = YES;
1216    else if (notificationObject == _input_recordTextField ||
1217            notificationObject == _input_postprocTextField)
1218        _inputSettingChanged = YES;
1219    else if (notificationObject == _osd_fontTextField ||
1220            notificationObject == _osd_langTextField ||
1221            notificationObject == _osd_opacityTextField)
1222        _osdSettingChanged = YES;
1223    else if (notificationObject == _video_snap_folderTextField ||
1224            notificationObject == _video_snap_prefixTextField)
1225        _videoSettingChanged = YES;
1226    else if (notificationObject == _intf_luahttppwdTextField)
1227        _intfSettingChanged = YES;
1228}
1229
1230- (IBAction)showFontPicker:(id)sender
1231{
1232    char * font = config_GetPsz(p_intf, "freetype-font");
1233    NSString * fontName = font ? toNSStr(font) : nil;
1234    free(font);
1235    if (fontName) {
1236        NSFont * font = [NSFont fontWithName:fontName size:0.0];
1237        [[NSFontManager sharedFontManager] setSelectedFont:font isMultiple:NO];
1238    }
1239    [[NSFontManager sharedFontManager] setTarget: self];
1240    [[NSFontPanel sharedFontPanel] setDelegate:self];
1241    [[NSFontPanel sharedFontPanel] makeKeyAndOrderFront:self];
1242}
1243
1244- (void)changeFont:(id)sender
1245{
1246    NSFont *someFont = [NSFont systemFontOfSize:12];
1247
1248    // converts given font to changes in font panel. Original font is irrelevant
1249    NSFont *selectedFont = [sender convertFont:someFont];
1250
1251    [_osd_fontTextField setStringValue:[selectedFont fontName]];
1252    [self osdSettingChanged:self];
1253}
1254
1255- (NSUInteger)validModesForFontPanel:(NSFontPanel *)fontPanel
1256{
1257    return NSFontPanelFaceModeMask | NSFontPanelCollectionModeMask;
1258}
1259
1260- (IBAction)inputSettingChanged:(id)sender
1261{
1262    if (sender == _input_cachelevelPopup) {
1263        if ([[[_input_cachelevelPopup selectedItem] title] isEqualToString: _NS("Custom")])
1264            [_input_cachelevel_customLabel setHidden: NO];
1265        else
1266            [_input_cachelevel_customLabel setHidden: YES];
1267    } else if (sender == _input_recordButton) {
1268        _selectFolderPanel = [[NSOpenPanel alloc] init];
1269        [_selectFolderPanel setCanChooseDirectories: YES];
1270        [_selectFolderPanel setCanChooseFiles: YES];
1271        [_selectFolderPanel setResolvesAliases: YES];
1272        [_selectFolderPanel setAllowsMultipleSelection: NO];
1273        [_selectFolderPanel setMessage: _NS("Choose the directory or filename where the records will be stored.")];
1274        [_selectFolderPanel setCanCreateDirectories: YES];
1275        [_selectFolderPanel setPrompt: _NS("Choose")];
1276        [_selectFolderPanel beginSheetModalForWindow:self.window completionHandler: ^(NSInteger returnCode) {
1277            if (returnCode == NSOKButton)
1278            {
1279                [_input_recordTextField setStringValue: [[_selectFolderPanel URL] path]];
1280                _inputSettingChanged = YES;
1281            }
1282        }];
1283
1284        return;
1285    }
1286
1287    _inputSettingChanged = YES;
1288}
1289
1290- (void)showInputSettings
1291{
1292    [self showSettingsForCategory:_inputView];
1293}
1294
1295- (NSString *)bundleIdentifierForApplicationName:(NSString *)appName
1296{
1297    NSWorkspace * workspace = [NSWorkspace sharedWorkspace];
1298    NSString * appPath = [workspace fullPathForApplication:appName];
1299    if (appPath) {
1300        NSBundle * appBundle = [NSBundle bundleWithPath:appPath];
1301        return [appBundle bundleIdentifier];
1302    }
1303    return nil;
1304}
1305
1306- (NSString *)applicationNameForBundleIdentifier:(NSString *)bundleIdentifier
1307{
1308    return [[[NSFileManager defaultManager] displayNameAtPath:[[NSWorkspace sharedWorkspace] absolutePathForAppBundleWithIdentifier:bundleIdentifier]] stringByDeletingPathExtension];
1309}
1310
1311- (NSImage *)iconForBundleIdentifier:(NSString *)bundleIdentifier
1312{
1313    NSWorkspace *workspace = [NSWorkspace sharedWorkspace];
1314    NSSize iconSize = NSMakeSize(16., 16.);
1315    NSImage *icon = [workspace iconForFile:[workspace absolutePathForAppBundleWithIdentifier:bundleIdentifier]];
1316    [icon setSize:iconSize];
1317    return icon;
1318}
1319
1320- (IBAction)urlHandlerAction:(id)sender
1321{
1322    NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
1323
1324    if (sender == _input_urlhandlerButton) {
1325
1326        void (^fillUrlHandlerPopup)(NSString*, NSPopUpButton*) = ^void(NSString *protocol, NSPopUpButton *object) {
1327
1328            NSArray *handlers = (__bridge_transfer NSArray *)LSCopyAllHandlersForURLScheme((__bridge CFStringRef)protocol);
1329            NSMutableArray *rawHandlers = [[NSMutableArray alloc] init];
1330            [object removeAllItems];
1331            NSUInteger count = [handlers count];
1332            for (NSUInteger x = 0; x < count; x++) {
1333                NSString *rawhandler = [handlers objectAtIndex:x];
1334                NSString *handler = [self applicationNameForBundleIdentifier:rawhandler];
1335                if (handler && ![handler isEqualToString:@""]) {
1336                    [object addItemWithTitle:handler];
1337                    [[object lastItem] setImage: [self iconForBundleIdentifier:[handlers objectAtIndex:x]]];
1338                    [rawHandlers addObject: rawhandler];
1339                }
1340            }
1341            [object selectItemAtIndex: [rawHandlers indexOfObject:(__bridge_transfer id)LSCopyDefaultHandlerForURLScheme((__bridge CFStringRef)protocol)]];
1342        };
1343
1344        fillUrlHandlerPopup(@"ftp", _urlhandler_ftpPopup);
1345        fillUrlHandlerPopup(@"mms", _urlhandler_mmsPopup);
1346        fillUrlHandlerPopup(@"rtmp", _urlhandler_rtmpPopup);
1347        fillUrlHandlerPopup(@"rtp", _urlhandler_rtpPopup);
1348        fillUrlHandlerPopup(@"rtsp", _urlhandler_rtspPopup);
1349        fillUrlHandlerPopup(@"sftp", _urlhandler_sftpPopup);
1350        fillUrlHandlerPopup(@"smb", _urlhandler_smbPopup);
1351        fillUrlHandlerPopup(@"udp", _urlhandler_udpPopup);
1352
1353
1354        [NSApp beginSheet:_urlhandler_win modalForWindow:self.window modalDelegate:self didEndSelector:NULL contextInfo:nil];
1355    } else {
1356        [_urlhandler_win orderOut:sender];
1357        [NSApp endSheet:_urlhandler_win];
1358
1359        if (sender == _urlhandler_saveButton) {
1360            LSSetDefaultHandlerForURLScheme(CFSTR("ftp"), (__bridge CFStringRef)[self bundleIdentifierForApplicationName:[[_urlhandler_ftpPopup selectedItem] title]]);
1361            LSSetDefaultHandlerForURLScheme(CFSTR("mms"), (__bridge CFStringRef)[self bundleIdentifierForApplicationName:[[_urlhandler_mmsPopup selectedItem] title]]);
1362            LSSetDefaultHandlerForURLScheme(CFSTR("mmsh"), (__bridge CFStringRef)[self bundleIdentifierForApplicationName:[[_urlhandler_mmsPopup selectedItem] title]]);
1363            LSSetDefaultHandlerForURLScheme(CFSTR("rtmp"), (__bridge CFStringRef)[self bundleIdentifierForApplicationName:[[_urlhandler_rtmpPopup selectedItem] title]]);
1364            LSSetDefaultHandlerForURLScheme(CFSTR("rtp"), (__bridge CFStringRef)[self bundleIdentifierForApplicationName:[[_urlhandler_rtpPopup selectedItem] title]]);
1365            LSSetDefaultHandlerForURLScheme(CFSTR("rtsp"), (__bridge CFStringRef)[self bundleIdentifierForApplicationName:[[_urlhandler_rtspPopup selectedItem] title]]);
1366            LSSetDefaultHandlerForURLScheme(CFSTR("sftp"), (__bridge CFStringRef)[self bundleIdentifierForApplicationName:[[_urlhandler_sftpPopup selectedItem] title]]);
1367            LSSetDefaultHandlerForURLScheme(CFSTR("smb"), (__bridge CFStringRef)[self bundleIdentifierForApplicationName:[[_urlhandler_smbPopup selectedItem] title]]);
1368            LSSetDefaultHandlerForURLScheme(CFSTR("udp"), (__bridge CFStringRef)[self bundleIdentifierForApplicationName:[[_urlhandler_udpPopup selectedItem] title]]);
1369        }
1370    }
1371}
1372
1373#pragma mark -
1374#pragma mark Hotkey actions
1375
1376- (void)hotkeyTableDoubleClick:(id)object
1377{
1378    // -1 is header
1379    if ([_hotkeys_listbox clickedRow] >= 0)
1380        [self hotkeySettingChanged:_hotkeys_listbox];
1381}
1382
1383- (IBAction)hotkeySettingChanged:(id)sender
1384{
1385    if (sender == _hotkeys_changeButton || sender == _hotkeys_listbox) {
1386        [_hotkeys_changeLabel setStringValue: [NSString stringWithFormat: _NS("Press new keys for\n\"%@\""),
1387                                               [_hotkeyDescriptions objectAtIndex:[_hotkeys_listbox selectedRow]]]];
1388        [_hotkeys_change_keysLabel setStringValue: [[VLCStringUtility sharedInstance] OSXStringKeyToString:[_hotkeySettings objectAtIndex:[_hotkeys_listbox selectedRow]]]];
1389        [_hotkeys_change_takenLabel setStringValue: @""];
1390        [_hotkeys_change_win setInitialFirstResponder: [_hotkeys_change_win contentView]];
1391        [_hotkeys_change_win makeFirstResponder: [_hotkeys_change_win contentView]];
1392        [NSApp runModalForWindow:_hotkeys_change_win];
1393    } else if (sender == _hotkeys_change_cancelButton) {
1394        [NSApp stopModal];
1395        [_hotkeys_change_win close];
1396    } else if (sender == _hotkeys_change_okButton) {
1397        NSInteger i_returnValue;
1398        if (! _keyInTransition) {
1399            [NSApp stopModal];
1400            [_hotkeys_change_win close];
1401            msg_Err(p_intf, "internal error prevented the hotkey switch");
1402            return;
1403        }
1404
1405        _hotkeyChanged = YES;
1406
1407        i_returnValue = [_hotkeySettings indexOfObject: _keyInTransition];
1408        if (i_returnValue != NSNotFound)
1409            [_hotkeySettings replaceObjectAtIndex: i_returnValue withObject: [NSString string]];
1410        NSString *tempString;
1411        tempString = [_keyInTransition stringByReplacingOccurrencesOfString:@"-" withString:@"+"];
1412        i_returnValue = [_hotkeySettings indexOfObject: tempString];
1413        if (i_returnValue != NSNotFound)
1414            [_hotkeySettings replaceObjectAtIndex: i_returnValue withObject: [NSString string]];
1415
1416        [_hotkeySettings replaceObjectAtIndex: [_hotkeys_listbox selectedRow] withObject:_keyInTransition];
1417
1418        [NSApp stopModal];
1419        [_hotkeys_change_win close];
1420
1421        [_hotkeys_listbox reloadData];
1422    } else if (sender == _hotkeys_clearButton) {
1423        [_hotkeySettings replaceObjectAtIndex: [_hotkeys_listbox selectedRow] withObject: [NSString string]];
1424        [_hotkeys_listbox reloadData];
1425        _hotkeyChanged = YES;
1426    }
1427}
1428
1429- (void)showHotkeySettings
1430{
1431    [self showSettingsForCategory:_hotkeysView];
1432}
1433
1434- (int)numberOfRowsInTableView:(NSTableView *)aTableView
1435{
1436    return [_hotkeySettings count];
1437}
1438
1439- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex
1440{
1441    NSString * identifier = [aTableColumn identifier];
1442
1443    if ([identifier isEqualToString: @"action"])
1444        return [_hotkeyDescriptions objectAtIndex:rowIndex];
1445    else if ([identifier isEqualToString: @"shortcut"])
1446        return [[VLCStringUtility sharedInstance] OSXStringKeyToString:[_hotkeySettings objectAtIndex:rowIndex]];
1447    else {
1448        msg_Err(p_intf, "unknown TableColumn identifier (%s)!", [identifier UTF8String]);
1449        return NULL;
1450    }
1451}
1452
1453- (BOOL)changeHotkeyTo: (NSString *)theKey
1454{
1455    NSInteger i_returnValue, i_returnValue2;
1456    i_returnValue = [_hotkeysNonUseableKeys indexOfObject: theKey];
1457
1458    if (i_returnValue != NSNotFound || [theKey isEqualToString:@""]) {
1459        [_hotkeys_change_keysLabel setStringValue: _NS("Invalid combination")];
1460        [_hotkeys_change_takenLabel setStringValue: _NS("Regrettably, these keys cannot be assigned as hotkey shortcuts.")];
1461        [_hotkeys_change_okButton setEnabled: NO];
1462        return NO;
1463    } else {
1464        [_hotkeys_change_keysLabel setStringValue: [[VLCStringUtility sharedInstance] OSXStringKeyToString:theKey]];
1465
1466        i_returnValue = [_hotkeySettings indexOfObject: theKey];
1467        i_returnValue2 = [_hotkeySettings indexOfObject: [theKey stringByReplacingOccurrencesOfString:@"-" withString:@"+"]];
1468        if (i_returnValue != NSNotFound)
1469            [_hotkeys_change_takenLabel setStringValue: [NSString stringWithFormat:
1470                                                         _NS("This combination is already taken by \"%@\"."),
1471                                                         [_hotkeyDescriptions objectAtIndex:i_returnValue]]];
1472        else if (i_returnValue2 != NSNotFound)
1473            [_hotkeys_change_takenLabel setStringValue: [NSString stringWithFormat:
1474                                                         _NS("This combination is already taken by \"%@\"."),
1475                                                         [_hotkeyDescriptions objectAtIndex:i_returnValue2]]];
1476        else
1477            [_hotkeys_change_takenLabel setStringValue: @""];
1478
1479        [_hotkeys_change_okButton setEnabled: YES];
1480        _keyInTransition = theKey;
1481        return YES;
1482    }
1483}
1484
1485@end
1486