1/*****************************************************************************
2 * VLCVideoEffectsWindowController.m: MacOS X interface module
3 *****************************************************************************
4 * Copyright (C) 2011-2015 Felix Paul Kühne
5 * $Id: e5904eb81cc903cc3c800ba3ebd0ce1cece192b3 $
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
24#import "CompatibilityFixes.h"
25#import "VLCMain.h"
26#import "VLCVideoEffectsWindowController.h"
27#import "VLCPopupPanelController.h"
28#import "VLCTextfieldPanelController.h"
29#import "VLCCoreInteraction.h"
30#import "VLCHexNumberFormatter.h"
31
32#define getWidgetBoolValue(w)   ((vlc_value_t){ .b_bool = [w state] })
33#define getWidgetIntValue(w)    ((vlc_value_t){ .i_int = [w intValue] })
34#define getWidgetFloatValue(w)  ((vlc_value_t){ .f_float = [w floatValue] })
35#define getWidgetStringValue(w) ((vlc_value_t){ .psz_string = (char *)[[w stringValue] UTF8String] })
36
37
38#pragma mark -
39#pragma mark Initialization
40
41@implementation VLCVideoEffectsWindowController
42
43+ (void)initialize
44{
45    /*
46     * Video effects profiles starting with 3.0:
47     * - Index 0 is assumed to be the default profile from previous versions
48     * - Index 0 from settings is never read or written anymore starting with 3.0, as the Default profile
49     *   is not persisted anymore.
50     */
51
52    NSDictionary *appDefaults = [NSDictionary dictionaryWithObjectsAndKeys:
53                                 [NSArray arrayWithObject:[VLCVideoEffectsWindowController defaultProfileString]], @"VideoEffectProfiles",
54                                 [NSArray arrayWithObject:_NS("Default")], @"VideoEffectProfileNames",
55                                 nil];
56    [[NSUserDefaults standardUserDefaults] registerDefaults:appDefaults];
57}
58
59+ (NSString *)defaultProfileString
60{
61    return @";;;0;1.000000;1.000000;1.000000;1.000000;0.050000;16;2.000000;OTA=;4;4;16711680;20;15;120;Z3JhZGllbnQ=;1;0;16711680;6;80;VkxD;-1;;-1;255;2;3;3;0;-180.000000";
62}
63
64- (id)init
65{
66    self = [super initWithWindowNibName:@"VideoEffects"];
67    if (self) {
68        self.popupPanel = [[VLCPopupPanelController alloc] init];
69        self.textfieldPanel = [[VLCTextfieldPanelController alloc] init];
70
71        NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
72        if ([defaults boolForKey:@"VideoEffectApplyProfileOnStartup"]) {
73            // This does not reset the UI (which does not exist yet), but it initalizes needed playlist vars
74            [self resetValues];
75
76            [self loadProfile];
77        } else {
78            [self saveCurrentProfileIndex:0];
79        }
80
81    }
82
83    return self;
84}
85
86/// Loads values from profile into variables
87- (void)loadProfile
88{
89    intf_thread_t *p_intf = getIntf();
90    playlist_t *p_playlist = pl_Get(p_intf);
91    VLCCoreInteraction *vci_si = [VLCCoreInteraction sharedInstance];
92    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
93    NSInteger profileIndex = [self currentProfileIndex];
94
95    NSString *profileString;
96    if (profileIndex == 0)
97        profileString = [VLCVideoEffectsWindowController defaultProfileString];
98    else
99        profileString = [[defaults objectForKey:@"VideoEffectProfiles"] objectAtIndex:profileIndex];
100
101    NSArray *items = [profileString componentsSeparatedByString:@";"];
102
103    // version 1 of profile string has 32 entries
104    if ([items count] < 32) {
105        msg_Err(p_intf, "Error in parsing profile string");
106        return;
107    }
108
109    /* filter handling */
110    NSString *tempString = B64DecNSStr([items firstObject]);
111    NSArray<NSValue *> *vouts = getVouts();
112
113    /* enable the new filters */
114    var_SetString(p_playlist, "video-filter", [tempString UTF8String]);
115    if (vouts)
116        for (NSValue *ptr in vouts) {
117            vout_thread_t *p_vout = [ptr pointerValue];
118            var_SetString(p_vout, "video-filter", [tempString UTF8String]);
119        }
120
121    tempString = B64DecNSStr([items objectAtIndex:1]);
122    /* enable another round of new filters */
123    var_SetString(p_playlist, "sub-source", [tempString UTF8String]);
124    if (vouts)
125        for (NSValue *ptr in vouts) {
126            vout_thread_t *p_vout = [ptr pointerValue];
127            var_SetString(p_vout, "sub-source", [tempString UTF8String]);
128            vlc_object_release(p_vout);
129        }
130
131    tempString = B64DecNSStr([items objectAtIndex:2]);
132    /* enable another round of new filters */
133    char *psz_current_splitter = var_GetString(p_playlist, "video-splitter");
134    bool b_filter_changed = ![tempString isEqualToString:toNSStr(psz_current_splitter)];
135    free(psz_current_splitter);
136
137    if (b_filter_changed)
138        var_SetString(p_playlist, "video-splitter", [tempString UTF8String]);
139
140    /* try to set filter values on-the-fly and store them appropriately */
141    // index 3 is deprecated
142    [vci_si setVideoFilterProperty: "contrast" forFilter: "adjust" withValue: getWidgetFloatValue([items objectAtIndex:4])];
143    [vci_si setVideoFilterProperty: "brightness" forFilter: "adjust" withValue: getWidgetFloatValue([items objectAtIndex:5])];
144    [vci_si setVideoFilterProperty: "saturation" forFilter: "adjust" withValue: getWidgetFloatValue([items objectAtIndex:6])];
145    [vci_si setVideoFilterProperty: "gamma" forFilter: "adjust" withValue: getWidgetFloatValue([items objectAtIndex:7])];
146    [vci_si setVideoFilterProperty: "sharpen-sigma" forFilter: "sharpen" withValue: getWidgetFloatValue([items objectAtIndex:8])];
147    [vci_si setVideoFilterProperty: "gradfun-radius" forFilter: "gradfun" withValue: getWidgetIntValue([items objectAtIndex:9])];
148    [vci_si setVideoFilterProperty: "grain-variance" forFilter: "grain" withValue: getWidgetFloatValue([items objectAtIndex:10])];
149    [vci_si setVideoFilterProperty: "transform-type" forFilter: "transform" withValue: (vlc_value_t){ .psz_string = (char *)[B64DecNSStr([items objectAtIndex:11]) UTF8String] }];
150    [vci_si setVideoFilterProperty: "puzzle-rows" forFilter: "puzzle" withValue: getWidgetIntValue([items objectAtIndex:12])];
151    [vci_si setVideoFilterProperty: "puzzle-cols" forFilter: "puzzle" withValue: getWidgetIntValue([items objectAtIndex:13])];
152    [vci_si setVideoFilterProperty: "colorthres-color" forFilter: "colorthres" withValue: getWidgetIntValue([items objectAtIndex:14])];
153    [vci_si setVideoFilterProperty: "colorthres-saturationthres" forFilter: "colorthres" withValue: getWidgetIntValue([items objectAtIndex:15])];
154    [vci_si setVideoFilterProperty: "colorthres-similaritythres" forFilter: "colorthres" withValue: getWidgetIntValue([items objectAtIndex:16])];
155    [vci_si setVideoFilterProperty: "sepia-intensity" forFilter: "sepia" withValue: getWidgetIntValue([items objectAtIndex:17])];
156    [vci_si setVideoFilterProperty: "gradient-mode" forFilter: "gradient" withValue: (vlc_value_t){ .psz_string = (char *)[B64DecNSStr([items objectAtIndex:18]) UTF8String] }];
157    [vci_si setVideoFilterProperty: "gradient-cartoon" forFilter: "gradient" withValue: (vlc_value_t){ .b_bool = [[items objectAtIndex:19] intValue] }];
158    [vci_si setVideoFilterProperty: "gradient-type" forFilter: "gradient" withValue: getWidgetIntValue([items objectAtIndex:20])];
159    [vci_si setVideoFilterProperty: "extract-component" forFilter: "extract" withValue: getWidgetIntValue([items objectAtIndex:21])];
160    [vci_si setVideoFilterProperty: "posterize-level" forFilter: "posterize" withValue: getWidgetIntValue([items objectAtIndex:22])];
161    [vci_si setVideoFilterProperty: "blur-factor" forFilter: "motionblur" withValue: getWidgetIntValue([items objectAtIndex:23])];
162    [vci_si setVideoFilterProperty: "marq-marquee" forFilter: "marq" withValue: (vlc_value_t){ .psz_string = (char *)[B64DecNSStr([items objectAtIndex:24]) UTF8String] }];
163    [vci_si setVideoFilterProperty: "marq-position" forFilter: "marq" withValue: getWidgetIntValue([items objectAtIndex:25])];
164    [vci_si setVideoFilterProperty: "logo-file" forFilter: "logo" withValue: (vlc_value_t){ .psz_string = (char *)[B64DecNSStr([items objectAtIndex:26]) UTF8String] }];
165    [vci_si setVideoFilterProperty: "logo-position" forFilter: "logo" withValue: getWidgetIntValue([items objectAtIndex:27])];
166    [vci_si setVideoFilterProperty: "logo-opacity" forFilter: "logo" withValue: getWidgetIntValue([items objectAtIndex:28])];
167    [vci_si setVideoFilterProperty: "clone-count" forFilter: "clone" withValue: getWidgetIntValue([items objectAtIndex:29])];
168    [vci_si setVideoFilterProperty: "wall-rows" forFilter: "wall" withValue: getWidgetIntValue([items objectAtIndex:30])];
169    [vci_si setVideoFilterProperty: "wall-cols" forFilter: "wall" withValue: getWidgetIntValue([items objectAtIndex:31])];
170
171    if ([items count] >= 33) { // version >=2 of profile string
172        [vci_si setVideoFilterProperty: "brightness-threshold" forFilter: "adjust" withValue: (vlc_value_t){ .b_bool = [[items objectAtIndex:32] intValue] }];
173    }
174
175    vlc_value_t hueValue;
176    if ([items count] >= 34) { // version >=3 of profile string
177        hueValue.f_float = [[items objectAtIndex:33] floatValue];
178    } else {
179        hueValue.f_float = [[items objectAtIndex:3] intValue]; // deprecated since 3.0.0
180        // convert to new scale ([0,360] --> [-180,180])
181        hueValue.f_float -= 180;
182    }
183    [vci_si setVideoFilterProperty: "hue" forFilter: "adjust" withValue: hueValue];
184}
185
186- (void)windowDidLoad
187{
188    [self.window setTitle: _NS("Video Effects")];
189    [self.window setExcludedFromWindowsMenu:YES];
190    [self.window setCollectionBehavior: NSWindowCollectionBehaviorFullScreenAuxiliary];
191
192    [_segmentView setLabel:_NS("Basic") forSegment:0];
193    [_segmentView setLabel:_NS("Crop") forSegment:1];
194    [_segmentView setLabel:_NS("Geometry") forSegment:2];
195    [_segmentView setLabel:_NS("Color") forSegment:3];
196    [_segmentView setLabel:_NS("Miscellaneous") forSegment:4];
197
198    [_applyProfileCheckbox setState:[[NSUserDefaults standardUserDefaults] boolForKey:@"VideoEffectApplyProfileOnStartup"]];
199    [_applyProfileCheckbox setTitle:_NS("Apply profile at next launch")];
200
201    [self resetProfileSelector];
202
203    [_adjustCheckbox setTitle:_NS("Image Adjust")];
204    [_adjustHueLabel setStringValue:_NS("Hue")];
205    [_adjustContrastLabel setStringValue:_NS("Contrast")];
206    [_adjustBrightnessLabel setStringValue:_NS("Brightness")];
207    [_adjustBrightnessCheckbox setTitle:_NS("Brightness Threshold")];
208    [_adjustSaturationLabel setStringValue:_NS("Saturation")];
209    [_adjustGammaLabel setStringValue:_NS("Gamma")];
210    [_adjustResetButton setTitle: _NS("Reset")];
211    [_sharpenCheckbox setTitle:_NS("Sharpen")];
212    [_sharpenLabel setStringValue:_NS("Sigma")];
213    [_bandingCheckbox setTitle:_NS("Banding removal")];
214    [_bandingLabel setStringValue:_NS("Radius")];
215    [_grainCheckbox setTitle:_NS("Film Grain")];
216    [_grainLabel setStringValue:_NS("Variance")];
217    [_cropTopLabel setStringValue:_NS("Top")];
218    [_cropLeftLabel setStringValue:_NS("Left")];
219    [_cropRightLabel setStringValue:_NS("Right")];
220    [_cropBottomLabel setStringValue:_NS("Bottom")];
221    [_cropSyncTopBottomCheckbox setTitle:_NS("Synchronize top and bottom")];
222    [_cropSyncLeftRightCheckbox setTitle:_NS("Synchronize left and right")];
223
224    [_transformCheckbox setTitle:_NS("Transform")];
225    [_transformPopup removeAllItems];
226    [_transformPopup addItemWithTitle: _NS("Rotate by 90 degrees")];
227    [[_transformPopup lastItem] setRepresentedObject: @"90"];
228    [[_transformPopup lastItem] setTag: 90];
229    [_transformPopup addItemWithTitle: _NS("Rotate by 180 degrees")];
230    [[_transformPopup lastItem] setRepresentedObject: @"180"];
231    [[_transformPopup lastItem] setTag: 180];
232    [_transformPopup addItemWithTitle: _NS("Rotate by 270 degrees")];
233    [[_transformPopup lastItem] setRepresentedObject: @"270"];
234    [[_transformPopup lastItem] setTag: 270];
235    [_transformPopup addItemWithTitle: _NS("Flip horizontally")];
236    [[_transformPopup lastItem] setRepresentedObject: @"hflip"];
237    [[_transformPopup lastItem] setTag: 1];
238    [_transformPopup addItemWithTitle: _NS("Flip vertically")];
239    [[_transformPopup lastItem] setRepresentedObject: @"vflip"];
240    [[_transformPopup lastItem] setTag: 2];
241    [_zoomCheckbox setTitle:_NS("Magnification/Zoom")];
242    [_puzzleCheckbox setTitle:_NS("Puzzle game")];
243    [_puzzleRowsLabel setStringValue:_NS("Rows")];
244    [_puzzleColumnsLabel setStringValue:_NS("Columns")];
245    [_cloneCheckbox setTitle:_NS("Clone")];
246    [_cloneNumberLabel setStringValue:_NS("Number of clones")];
247    [_wallCheckbox setTitle:_NS("Wall")];
248    [_wallNumbersOfRowsLabel setStringValue:_NS("Rows")];
249    [_wallNumberOfColumnsLabel setStringValue:_NS("Columns")];
250
251    [_thresholdCheckbox setTitle:_NS("Color threshold")];
252    [_thresholdColorLabel setStringValue:_NS("Color")];
253    [_thresholdColorTextField setFormatter:[[VLCHexNumberFormatter alloc] init]];
254    [_thresholdSaturationLabel setStringValue:_NS("Saturation")];
255    [_thresholdSimilarityLabel setStringValue:_NS("Similarity")];
256    [_sepiaCheckbox setTitle:_NS("Sepia")];
257    [_sepiaLabel setStringValue:_NS("Intensity")];
258    [_gradientCheckbox setTitle:_NS("Gradient")];
259    [_gradientModeLabel setStringValue:_NS("Mode")];
260    [_gradientModePopup removeAllItems];
261    [_gradientModePopup addItemWithTitle: _NS("Gradient")];
262    [[_gradientModePopup lastItem] setRepresentedObject: @"gradient"];
263    [[_gradientModePopup lastItem] setTag: 1];
264    [_gradientModePopup addItemWithTitle: _NS("Edge")];
265    [[_gradientModePopup lastItem] setRepresentedObject: @"edge"];
266    [[_gradientModePopup lastItem] setTag: 2];
267    [_gradientModePopup addItemWithTitle: _NS("Hough")];
268    [[_gradientModePopup lastItem] setRepresentedObject: @"hough"];
269    [[_gradientModePopup lastItem] setTag: 3];
270    [_gradientColorCheckbox setTitle:_NS("Color")];
271    [_gradientCartoonCheckbox setTitle:_NS("Cartoon")];
272    [_extractCheckbox setTitle:_NS("Color extraction")];
273    [_extractLabel setStringValue:_NS("Color")];
274    [_extractTextField setFormatter:[[VLCHexNumberFormatter alloc] init]];
275    [_invertCheckbox setTitle:_NS("Invert colors")];
276    [_posterizeCheckbox setTitle:_NS("Posterize")];
277    [_posterizeLabel setStringValue:_NS("Posterize level")];
278    [_blurCheckbox setTitle:_NS("Motion blur")];
279    [_blurLabel setStringValue:_NS("Factor")];
280    [_motiondetectCheckbox setTitle:_NS("Motion Detect")];
281    [_watereffectCheckbox setTitle:_NS("Water effect")];
282    [_wavesCheckbox setTitle:_NS("Waves")];
283    [_psychedelicCheckbox setTitle:_NS("Psychedelic")];
284    [_anaglyphCheckbox setTitle:_NS("Anaglyph")];
285    [_addTextCheckbox setTitle:_NS("Add text")];
286    [_addTextTextLabel setStringValue:_NS("Text")];
287    [_addTextPositionLabel setStringValue:_NS("Position")];
288    [_addTextPositionPopup removeAllItems];
289    [_addTextPositionPopup addItemWithTitle: _NS("Center")];
290    [[_addTextPositionPopup lastItem] setTag: 0];
291    [_addTextPositionPopup addItemWithTitle: _NS("Left")];
292    [[_addTextPositionPopup lastItem] setTag: 1];
293    [_addTextPositionPopup addItemWithTitle: _NS("Right")];
294    [[_addTextPositionPopup lastItem] setTag: 2];
295    [_addTextPositionPopup addItemWithTitle: _NS("Top")];
296    [[_addTextPositionPopup lastItem] setTag: 4];
297    [_addTextPositionPopup addItemWithTitle: _NS("Bottom")];
298    [[_addTextPositionPopup lastItem] setTag: 8];
299    [_addTextPositionPopup addItemWithTitle: _NS("Top-Left")];
300    [[_addTextPositionPopup lastItem] setTag: 5];
301    [_addTextPositionPopup addItemWithTitle: _NS("Top-Right")];
302    [[_addTextPositionPopup lastItem] setTag: 6];
303    [_addTextPositionPopup addItemWithTitle: _NS("Bottom-Left")];
304    [[_addTextPositionPopup lastItem] setTag: 9];
305    [_addTextPositionPopup addItemWithTitle: _NS("Bottom-Right")];
306    [[_addTextPositionPopup lastItem] setTag: 10];
307    [_addLogoCheckbox setTitle:_NS("Add logo")];
308    [_addLogoLogoLabel setStringValue:_NS("Logo")];
309    [_addLogoPositionLabel setStringValue:_NS("Position")];
310    [_addLogoPositionPopup removeAllItems];
311    [_addLogoPositionPopup addItemWithTitle: _NS("Center")];
312    [[_addLogoPositionPopup lastItem] setTag: 0];
313    [_addLogoPositionPopup addItemWithTitle: _NS("Left")];
314    [[_addLogoPositionPopup lastItem] setTag: 1];
315    [_addLogoPositionPopup addItemWithTitle: _NS("Right")];
316    [[_addLogoPositionPopup lastItem] setTag: 2];
317    [_addLogoPositionPopup addItemWithTitle: _NS("Top")];
318    [[_addLogoPositionPopup lastItem] setTag: 4];
319    [_addLogoPositionPopup addItemWithTitle: _NS("Bottom")];
320    [[_addLogoPositionPopup lastItem] setTag: 8];
321    [_addLogoPositionPopup addItemWithTitle: _NS("Top-Left")];
322    [[_addLogoPositionPopup lastItem] setTag: 5];
323    [_addLogoPositionPopup addItemWithTitle: _NS("Top-Right")];
324    [[_addLogoPositionPopup lastItem] setTag: 6];
325    [_addLogoPositionPopup addItemWithTitle: _NS("Bottom-Left")];
326    [[_addLogoPositionPopup lastItem] setTag: 9];
327    [_addLogoPositionPopup addItemWithTitle: _NS("Bottom-Right")];
328    [[_addLogoPositionPopup lastItem] setTag: 10];
329    [_addLogoTransparencyLabel setStringValue:_NS("Transparency")];
330
331    [[NSNotificationCenter defaultCenter] addObserver:self
332                                             selector:@selector(inputChangedEvent:)
333                                                 name:VLCInputChangedNotification
334                                               object:nil];
335
336    [self resetValues];
337}
338
339- (void)dealloc
340{
341    [[NSNotificationCenter defaultCenter] removeObserver:self];
342}
343
344- (void)updateCocoaWindowLevel:(NSInteger)i_level
345{
346    if (self.isWindowLoaded && [self.window isVisible] && [self.window level] != i_level)
347        [self.window setLevel: i_level];
348}
349
350#pragma mark -
351#pragma mark internal functions
352
353- (void)saveCurrentProfileIndex:(NSInteger)index
354{
355    [[NSUserDefaults standardUserDefaults] setInteger:index forKey:@"VideoEffectSelectedProfile"];
356}
357
358- (NSInteger)currentProfileIndex
359{
360    return [[NSUserDefaults standardUserDefaults] integerForKey:@"VideoEffectSelectedProfile"];
361}
362
363/// Returns the list of profile names (omitting the Default entry)
364- (NSArray *)nonDefaultProfileNames
365{
366    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
367
368    NSMutableArray *names = [[defaults stringArrayForKey:@"VideoEffectProfileNames"] mutableCopy];
369    [names removeObjectAtIndex:0];
370    return [names copy];
371}
372
373-(void)inputChangedEvent:(NSNotification *)o_notification
374{
375    // reset crop values when input changed
376    [self setCropBottomValue:0];
377    [self setCropTopValue:0];
378    [self setCropLeftValue:0];
379    [self setCropRightValue:0];
380}
381
382- (void)resetProfileSelector
383{
384    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
385    [_profilePopup removeAllItems];
386
387    // Ignore "Default" index 0 from settings
388    [_profilePopup addItemWithTitle:_NS("Default")];
389
390    [_profilePopup addItemsWithTitles:[self nonDefaultProfileNames]];
391
392    [[_profilePopup menu] addItem:[NSMenuItem separatorItem]];
393    [_profilePopup addItemWithTitle:_NS("Duplicate current profile...")];
394    [[_profilePopup lastItem] setTarget: self];
395    [[_profilePopup lastItem] setAction: @selector(addProfile:)];
396
397    if ([[self nonDefaultProfileNames] count] > 0) {
398        [_profilePopup addItemWithTitle:_NS("Organize profiles...")];
399        [[_profilePopup lastItem] setTarget: self];
400        [[_profilePopup lastItem] setAction: @selector(removeProfile:)];
401    }
402
403    [_profilePopup selectItemAtIndex: [self currentProfileIndex]];
404    // Loading only non-default profiles ensures that vlcrc or command line settings are not overwritten
405    if ([self currentProfileIndex] > 0)
406        [self profileSelectorAction:self];
407}
408
409- (void)setWidgetValue: (id)widget forOption: (char *)psz_option enabled: (bool)b_state
410{
411    intf_thread_t *p_intf = getIntf();
412    playlist_t *p_playlist = pl_Get(p_intf);
413
414    vlc_value_t val;
415    int i_type = config_GetType(psz_option) & VLC_VAR_CLASS;
416    switch (i_type)
417    {
418    case VLC_VAR_BOOL:
419    case VLC_VAR_INTEGER:
420    case VLC_VAR_FLOAT:
421    case VLC_VAR_STRING:
422        break;
423    default:
424        msg_Err(p_intf, "%s variable is of an unsupported type (%d)", psz_option, i_type);
425        return;
426    }
427
428    if (var_Create(p_playlist, psz_option, i_type | VLC_VAR_DOINHERIT) ||
429        var_GetChecked(p_playlist, psz_option, i_type, &val)) {
430        return;
431    }
432
433    if (i_type == VLC_VAR_BOOL || i_type == VLC_VAR_INTEGER)
434    {
435        if ([widget isKindOfClass: [NSSlider class]])
436        {
437            [widget setIntValue: val.i_int];
438            [widget setToolTip: [NSString stringWithFormat:@"%lli", val.i_int]];
439        }
440        else if ([widget isKindOfClass: [NSButton class]])
441            [widget setState: val.i_int ? NSOnState : NSOffState];
442        else if ([widget isKindOfClass: [NSTextField class]])
443            [widget setIntValue: val.i_int];
444        else if ([widget isKindOfClass: [NSStepper class]])
445            [widget setIntValue: val.i_int];
446        else if ([widget isKindOfClass: [NSPopUpButton class]])
447            [widget selectItemWithTag: val.i_int];
448    }
449    else if (i_type == VLC_VAR_FLOAT)
450    {
451        if ([widget isKindOfClass: [NSSlider class]])
452        {
453            [widget setFloatValue: val.f_float];
454            [widget setToolTip: [NSString stringWithFormat:@"%0.3f", val.f_float]];
455        }
456    }
457    else if (i_type == VLC_VAR_STRING)
458    {
459        if ([widget isKindOfClass: [NSPopUpButton class]])
460        {
461            for (NSMenuItem *item in [widget itemArray])
462                if ([item representedObject] &&
463                    !strcmp([[item representedObject] UTF8String], val.psz_string))
464                {
465                    [widget selectItemWithTitle: [item title]];
466                    break;
467                }
468        }
469        else if ([widget isKindOfClass: [NSTextField class]])
470            [widget setStringValue: toNSStr(val.psz_string)];
471        free(val.psz_string);
472    }
473
474    [widget setEnabled: b_state];
475}
476
477/// Sets widget values based on variables
478- (void)resetValues
479{
480    intf_thread_t *p_intf = getIntf();
481    playlist_t *p_playlist = pl_Get(p_intf);
482    NSString *tmpString;
483    char *tmpChar;
484    BOOL b_state;
485
486    /* do we have any filter enabled? if yes, show it. */
487    char * psz_vfilters;
488    psz_vfilters = var_InheritString(p_playlist, "video-filter");
489    if (psz_vfilters) {
490        [_adjustCheckbox setState: (NSInteger)strstr(psz_vfilters, "adjust")];
491        [_sharpenCheckbox setState: (NSInteger)strstr(psz_vfilters, "sharpen")];
492        [_bandingCheckbox setState: (NSInteger)strstr(psz_vfilters, "gradfun")];
493        [_grainCheckbox setState: (NSInteger)strstr(psz_vfilters, "grain")];
494        [_transformCheckbox setState: (NSInteger)strstr(psz_vfilters, "transform")];
495        [_zoomCheckbox setState: (NSInteger)strstr(psz_vfilters, "magnify")];
496        [_puzzleCheckbox setState: (NSInteger)strstr(psz_vfilters, "puzzle")];
497        [_thresholdCheckbox setState: (NSInteger)strstr(psz_vfilters, "colorthres")];
498        [_sepiaCheckbox setState: (NSInteger)strstr(psz_vfilters, "sepia")];
499        [_gradientCheckbox setState: (NSInteger)strstr(psz_vfilters, "gradient")];
500        [_extractCheckbox setState: (NSInteger)strstr(psz_vfilters, "extract")];
501        [_invertCheckbox setState: (NSInteger)strstr(psz_vfilters, "invert")];
502        [_posterizeCheckbox setState: (NSInteger)strstr(psz_vfilters, "posterize")];
503        [_blurCheckbox setState: (NSInteger)strstr(psz_vfilters, "motionblur")];
504        [_motiondetectCheckbox setState: (NSInteger)strstr(psz_vfilters, "motiondetect")];
505        [_watereffectCheckbox setState: (NSInteger)strstr(psz_vfilters, "ripple")];
506        [_wavesCheckbox setState: (NSInteger)strstr(psz_vfilters, "wave")];
507        [_psychedelicCheckbox setState: (NSInteger)strstr(psz_vfilters, "psychedelic")];
508        [_anaglyphCheckbox setState: (NSInteger)strstr(psz_vfilters, "anaglyph")];
509        free(psz_vfilters);
510    } else {
511        [_adjustCheckbox setState: NSOffState];
512        [_sharpenCheckbox setState: NSOffState];
513        [_bandingCheckbox setState: NSOffState];
514        [_grainCheckbox setState: NSOffState];
515        [_transformCheckbox setState: NSOffState];
516        [_zoomCheckbox setState: NSOffState];
517        [_puzzleCheckbox setState: NSOffState];
518        [_thresholdCheckbox setState: NSOffState];
519        [_sepiaCheckbox setState: NSOffState];
520        [_gradientCheckbox setState: NSOffState];
521        [_extractCheckbox setState: NSOffState];
522        [_invertCheckbox setState: NSOffState];
523        [_posterizeCheckbox setState: NSOffState];
524        [_blurCheckbox setState: NSOffState];
525        [_motiondetectCheckbox setState: NSOffState];
526        [_watereffectCheckbox setState: NSOffState];
527        [_wavesCheckbox setState: NSOffState];
528        [_psychedelicCheckbox setState: NSOffState];
529        [_anaglyphCheckbox setState: NSOffState];
530    }
531
532    psz_vfilters = var_InheritString(p_playlist, "sub-source");
533    if (psz_vfilters) {
534        [_addTextCheckbox setState: (NSInteger)strstr(psz_vfilters, "marq")];
535        [_addLogoCheckbox setState: (NSInteger)strstr(psz_vfilters, "logo")];
536        free(psz_vfilters);
537    } else {
538        [_addTextCheckbox setState: NSOffState];
539        [_addLogoCheckbox setState: NSOffState];
540    }
541
542    psz_vfilters = var_InheritString(p_playlist, "video-splitter");
543    if (psz_vfilters) {
544        [_cloneCheckbox setState: (NSInteger)strstr(psz_vfilters, "clone")];
545        [_wallCheckbox setState: (NSInteger)strstr(psz_vfilters, "wall")];
546        free(psz_vfilters);
547    } else {
548        [_cloneCheckbox setState: NSOffState];
549        [_wallCheckbox setState: NSOffState];
550    }
551
552    /* fetch and show the various values */
553    b_state = [_adjustCheckbox state];
554    [self setWidgetValue: _adjustHueSlider forOption: "hue" enabled: b_state];
555    [self setWidgetValue: _adjustContrastSlider forOption: "contrast" enabled: b_state];
556    [self setWidgetValue: _adjustBrightnessSlider forOption: "brightness" enabled: b_state];
557    [self setWidgetValue: _adjustSaturationSlider forOption: "saturation" enabled: b_state];
558    [self setWidgetValue: _adjustBrightnessCheckbox forOption: "brightness-threshold" enabled: b_state];
559    [self setWidgetValue: _adjustGammaSlider forOption: "gamma" enabled: b_state];
560    [_adjustBrightnessLabel setEnabled: b_state];
561    [_adjustContrastLabel setEnabled: b_state];
562    [_adjustGammaLabel setEnabled: b_state];
563    [_adjustHueLabel setEnabled: b_state];
564    [_adjustSaturationLabel setEnabled: b_state];
565    [_adjustResetButton setEnabled: b_state];
566
567    [self setWidgetValue: _sharpenSlider forOption: "sharpen-sigma" enabled: [_sharpenCheckbox state]];
568    [_sharpenLabel setEnabled: [_sharpenCheckbox state]];
569
570    [self setWidgetValue: _bandingSlider forOption: "gradfun-radius" enabled: [_bandingCheckbox state]];
571    [_bandingLabel setEnabled: [_bandingCheckbox state]];
572
573    [self setWidgetValue: _grainSlider forOption: "grain-variance" enabled: [_grainCheckbox state]];
574    [_grainLabel setEnabled: [_grainCheckbox state]];
575
576    [self setCropLeftValue: 0];
577    [self setCropTopValue: 0];
578    [self setCropRightValue: 0];
579    [self setCropBottomValue: 0];
580    [_cropSyncTopBottomCheckbox setState: NSOffState];
581    [_cropSyncLeftRightCheckbox setState: NSOffState];
582
583    [self setWidgetValue: _transformPopup forOption: "transform-type" enabled: [_transformCheckbox state]];
584
585    b_state = [_puzzleCheckbox state];
586    [self setWidgetValue: _puzzleColumnsTextField forOption: "puzzle-cols" enabled: b_state];
587    [self setWidgetValue: _puzzleColumnsStepper forOption: "puzzle-cols" enabled: b_state];
588    [self setWidgetValue: _puzzleRowsTextField forOption: "puzzle-rows" enabled: b_state];
589    [self setWidgetValue: _puzzleRowsStepper forOption: "puzzle-rows" enabled: b_state];
590    [_puzzleRowsLabel setEnabled: b_state];
591    [_puzzleColumnsLabel setEnabled: b_state];
592
593    b_state = [_cloneCheckbox state];
594    [self setWidgetValue: _cloneNumberTextField forOption: "clone-count" enabled: b_state];
595    [self setWidgetValue: _cloneNumberStepper forOption: "clone-count" enabled: b_state];
596    [_cloneNumberLabel setEnabled: b_state];
597
598    b_state = [_wallCheckbox state];
599    [self setWidgetValue: _wallNumbersOfRowsTextField forOption: "wall-rows" enabled: b_state];
600    [self setWidgetValue: _wallNumbersOfRowsStepper forOption: "wall-rows" enabled: b_state];
601    [self setWidgetValue: _wallNumberOfColumnsTextField forOption: "wall-cols" enabled: b_state];
602    [self setWidgetValue: _wallNumberOfColumnsStepper forOption: "wall-cols" enabled: b_state];
603    [_wallNumbersOfRowsLabel setEnabled: b_state];
604    [_wallNumberOfColumnsLabel setEnabled: b_state];
605
606    b_state = [_thresholdCheckbox state];
607    [self setWidgetValue: _thresholdColorTextField forOption: "colorthres-color" enabled: b_state];
608    [self setWidgetValue: _thresholdSaturationSlider forOption: "colorthres-saturationthres" enabled: b_state];
609    [self setWidgetValue: _thresholdSimilaritySlider forOption: "colorthres-similaritythres" enabled: b_state];
610    [_thresholdColorLabel setEnabled: b_state];
611    [_thresholdSaturationLabel setEnabled: b_state];
612    [_thresholdSimilarityLabel setEnabled: b_state];
613
614    b_state = [_sepiaCheckbox state];
615    [self setWidgetValue: _sepiaTextField forOption: "sepia-intensity" enabled: b_state];
616    [self setWidgetValue: _sepiaStepper forOption: "sepia-intensity" enabled: b_state];
617    [_sepiaLabel setEnabled: b_state];
618
619    b_state = [_gradientCheckbox state];
620    [self setWidgetValue: _gradientModePopup forOption: "gradient-mode" enabled: b_state];
621    [self setWidgetValue: _gradientCartoonCheckbox forOption: "gradient-cartoon" enabled: b_state];
622    [self setWidgetValue: _gradientColorCheckbox forOption: "gradient-type" enabled: b_state];
623    [_gradientModeLabel setEnabled: b_state];
624
625    [self setWidgetValue: _extractTextField forOption: "extract-component" enabled: [_extractCheckbox state]];
626    [_extractLabel setEnabled: [_extractCheckbox state]];
627
628    b_state = [_posterizeCheckbox state];
629    [self setWidgetValue: _posterizeTextField forOption: "posterize-level" enabled: b_state];
630    [self setWidgetValue: _posterizeStepper forOption: "posterize-level" enabled: b_state];
631    [_posterizeLabel setEnabled: b_state];
632
633    [self setWidgetValue: _blurSlider forOption: "blur-factor" enabled: [_blurCheckbox state]];
634    [_blurLabel setEnabled: [_blurCheckbox state]];
635
636    b_state = [_addTextCheckbox state];
637    [self setWidgetValue: _addTextTextTextField forOption: "marq-marquee" enabled: b_state];
638    [self setWidgetValue: _addTextPositionPopup forOption: "marq-position" enabled: b_state];
639    [_addTextPositionLabel setEnabled: b_state];
640    [_addTextTextLabel setEnabled: b_state];
641
642    b_state = [_addLogoCheckbox state];
643    [self setWidgetValue: _addLogoLogoTextField forOption: "logo-file" enabled: b_state];
644    [self setWidgetValue: _addLogoPositionPopup forOption: "logo-position" enabled: b_state];
645    [self setWidgetValue: _addLogoTransparencySlider forOption: "logo-opacity" enabled: b_state];
646    [_addLogoPositionLabel setEnabled: b_state];
647    [_addLogoLogoLabel setEnabled: b_state];
648    [_addLogoTransparencyLabel setEnabled: b_state];
649}
650
651- (NSString *)generateProfileString
652{
653    intf_thread_t *p_intf = getIntf();
654    playlist_t *p_playlist = pl_Get(p_intf);
655    return [NSString stringWithFormat:@"%@;%@;%@;%lli;%f;%f;%f;%f;%f;%lli;%f;%@;%lli;%lli;%lli;%lli;%lli;%lli;%@;%lli;%lli;%lli;%lli;%lli;%@;%lli;%@;%lli;%lli;%lli;%lli;%lli;%lli;%f",
656                     B64EncAndFree(var_InheritString(p_playlist, "video-filter")),
657                     B64EncAndFree(var_InheritString(p_playlist, "sub-source")),
658                     B64EncAndFree(var_InheritString(p_playlist, "video-splitter")),
659                     0LL, // former "hue" value, deprecated since 3.0.0
660                     var_InheritFloat(p_playlist, "contrast"),
661                     var_InheritFloat(p_playlist, "brightness"),
662                     var_InheritFloat(p_playlist, "saturation"),
663                     var_InheritFloat(p_playlist, "gamma"),
664                     var_InheritFloat(p_playlist, "sharpen-sigma"),
665                     var_InheritInteger(p_playlist, "gradfun-radius"),
666                     var_InheritFloat(p_playlist, "grain-variance"),
667                     B64EncAndFree(var_InheritString(p_playlist, "transform-type")),
668                     var_InheritInteger(p_playlist, "puzzle-rows"),
669                     var_InheritInteger(p_playlist, "puzzle-cols"),
670                     var_InheritInteger(p_playlist, "colorthres-color"),
671                     var_InheritInteger(p_playlist, "colorthres-saturationthres"),
672                     var_InheritInteger(p_playlist, "colorthres-similaritythres"),
673                     var_InheritInteger(p_playlist, "sepia-intensity"),
674                     B64EncAndFree(var_InheritString(p_playlist, "gradient-mode")),
675                     (int64_t)var_InheritBool(p_playlist, "gradient-cartoon"),
676                     var_InheritInteger(p_playlist, "gradient-type"),
677                     var_InheritInteger(p_playlist, "extract-component"),
678                     var_InheritInteger(p_playlist, "posterize-level"),
679                     var_InheritInteger(p_playlist, "blur-factor"),
680                     B64EncAndFree(var_InheritString(p_playlist, "marq-marquee")),
681                     var_InheritInteger(p_playlist, "marq-position"),
682                     B64EncAndFree(var_InheritString(p_playlist, "logo-file")),
683                     var_InheritInteger(p_playlist, "logo-position"),
684                     var_InheritInteger(p_playlist, "logo-opacity"),
685                     var_InheritInteger(p_playlist, "clone-count"),
686                     var_InheritInteger(p_playlist, "wall-rows"),
687                     var_InheritInteger(p_playlist, "wall-cols"),
688                     // version 2 of profile string:
689                     (int64_t)var_InheritBool(p_playlist, "brightness-threshold"), // index: 32
690                     // version 3 of profile string: (vlc-3.0.0)
691                     var_InheritFloat(p_playlist, "hue") // index: 33
692            ];
693}
694
695#pragma mark -
696#pragma mark generic UI code
697
698- (void)saveCurrentProfile
699{
700    NSInteger currentProfileIndex = [self currentProfileIndex];
701
702    // Do not save default profile
703    if (currentProfileIndex == 0) {
704        return;
705    }
706
707    /* fetch all the current settings in a uniform string */
708    NSString *newProfile = [self generateProfileString];
709
710    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
711    NSMutableArray *workArray = [[NSMutableArray alloc] initWithArray:[defaults objectForKey:@"VideoEffectProfiles"]];
712    if (currentProfileIndex >= [workArray count])
713        return;
714
715    [workArray replaceObjectAtIndex:currentProfileIndex withObject:newProfile];
716    [defaults setObject:[NSArray arrayWithArray:workArray] forKey:@"VideoEffectProfiles"];
717    [defaults synchronize];
718}
719
720- (void)saveCurrentProfileAtTerminate
721{
722    if ([self currentProfileIndex] > 0) {
723        [self saveCurrentProfile];
724        return;
725    }
726
727    // A new "Custom profile" is only created if the user wants to load this as new profile at startup ...
728    if (_applyProfileCheckbox.state == NSOffState)
729        return;
730
731    // ... and some settings are changed
732    NSString *newProfile = [self generateProfileString];
733    if ([newProfile compare:[VLCVideoEffectsWindowController defaultProfileString]] == NSOrderedSame)
734        return;
735
736    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
737    NSMutableArray *workArray = [[NSMutableArray alloc] initWithArray:[defaults objectForKey:@"VideoEffectProfiles"]];
738    [workArray addObject:newProfile];
739    [defaults setObject:[NSArray arrayWithArray:workArray] forKey:@"VideoEffectProfiles"];
740
741    NSArray<NSString *> *profileNames = [defaults objectForKey:@"VideoEffectProfileNames"];
742    NSString *newProfileName;
743
744    unsigned int num_custom = 0;
745    do
746        newProfileName = [@"Custom" stringByAppendingString:[NSString stringWithFormat:@"%03i",num_custom++]];
747    while ([profileNames containsObject:newProfileName]);
748
749    workArray = [[NSMutableArray alloc] initWithArray:[defaults objectForKey:@"VideoEffectProfileNames"]];
750    [workArray addObject:newProfileName];
751    [defaults setObject:[NSArray arrayWithArray:workArray] forKey:@"VideoEffectProfileNames"];
752
753    [self saveCurrentProfileIndex:([workArray count] - 1)];
754
755    [defaults synchronize];
756}
757
758- (IBAction)toggleWindow:(id)sender
759{
760    if ([self.window isKeyWindow])
761        [self.window orderOut:sender];
762    else {
763        [self.window setLevel: [[[VLCMain sharedInstance] voutController] currentStatusWindowLevel]];
764        [self.window makeKeyAndOrderFront:sender];
765    }
766}
767
768- (IBAction)profileSelectorAction:(id)sender
769{
770    [self saveCurrentProfile];
771
772    [self saveCurrentProfileIndex:[_profilePopup indexOfSelectedItem]];
773
774    [self loadProfile];
775    [self resetValues];
776}
777
778- (void)addProfile:(id)sender
779{
780    /* show panel */
781#pragma clang diagnostic push
782#pragma clang diagnostic ignored "-Wpartial-availability"
783#ifdef MAC_OS_X_VERSION_10_10
784    if (OSX_YOSEMITE_AND_HIGHER) {
785        [[_textfieldPanel window] setAppearance:[NSAppearance appearanceNamed:NSAppearanceNameVibrantDark]];
786    }
787#endif
788#pragma clang diagnostic pop
789    [_textfieldPanel setTitleString:_NS("Duplicate current profile for a new profile")];
790    [_textfieldPanel setSubTitleString:_NS("Enter a name for the new profile:")];
791    [_textfieldPanel setCancelButtonString:_NS("Cancel")];
792    [_textfieldPanel setOkButtonString:_NS("Save")];
793
794    // TODO: Change to weak, when dropping 10.7 support
795    __unsafe_unretained typeof(self) _self = self;
796    [_textfieldPanel runModalForWindow:self.window completionHandler:^(NSInteger returnCode, NSString *resultingText) {
797
798        NSInteger currentProfileIndex = [_self currentProfileIndex];
799        if (returnCode != NSOKButton) {
800            [_profilePopup selectItemAtIndex:currentProfileIndex];
801            return;
802        }
803
804        NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
805        NSArray *profileNames = [defaults objectForKey:@"VideoEffectProfileNames"];
806
807        // duplicate names are not allowed in the popup control
808        if ([resultingText length] == 0 || [profileNames containsObject:resultingText]) {
809            [_profilePopup selectItemAtIndex:currentProfileIndex];
810
811            NSAlert *alert = [[NSAlert alloc] init];
812            [alert setAlertStyle:NSCriticalAlertStyle];
813            [alert setMessageText:_NS("Please enter a unique name for the new profile.")];
814            [alert setInformativeText:_NS("Multiple profiles with the same name are not allowed.")];
815
816            [alert beginSheetModalForWindow:_self.window
817                              modalDelegate:nil
818                             didEndSelector:nil
819                                contextInfo:nil];
820            return;
821        }
822
823        /* fetch all the current settings in a uniform string */
824        NSString *newProfile = [_self generateProfileString];
825
826        /* add string to user defaults as well as a label */
827
828        NSMutableArray *workArray = [[NSMutableArray alloc] initWithArray:[defaults objectForKey:@"VideoEffectProfiles"]];
829        [workArray addObject:newProfile];
830        [defaults setObject:[NSArray arrayWithArray:workArray] forKey:@"VideoEffectProfiles"];
831
832        [self saveCurrentProfileIndex:([workArray count] - 1)];
833
834        workArray = [[NSMutableArray alloc] initWithArray:[defaults objectForKey:@"VideoEffectProfileNames"]];
835        [workArray addObject:resultingText];
836        [defaults setObject:[NSArray arrayWithArray:workArray] forKey:@"VideoEffectProfileNames"];
837
838        /* save defaults */
839        [defaults synchronize];
840
841        /* refresh UI */
842        [_self resetProfileSelector];
843    }];
844}
845
846- (void)removeProfile:(id)sender
847{
848    /* show panel */
849#pragma clang diagnostic push
850#pragma clang diagnostic ignored "-Wpartial-availability"
851#ifdef MAC_OS_X_VERSION_10_10
852    if (OSX_YOSEMITE_AND_HIGHER) {
853        [[_popupPanel window] setAppearance:[NSAppearance appearanceNamed:NSAppearanceNameVibrantDark]];
854    }
855#endif
856#pragma clang diagnostic pop
857    [_popupPanel setTitleString:_NS("Remove a preset")];
858    [_popupPanel setSubTitleString:_NS("Select the preset you would like to remove:")];
859    [_popupPanel setOkButtonString:_NS("Remove")];
860    [_popupPanel setCancelButtonString:_NS("Cancel")];
861    [_popupPanel setPopupButtonContent:[self nonDefaultProfileNames]];
862
863    // TODO: Change to weak, when dropping 10.7 support
864    __unsafe_unretained typeof(self) _self = self;
865    [_popupPanel runModalForWindow:self.window completionHandler:^(NSInteger returnCode, NSInteger selectedIndex) {
866
867
868        NSInteger activeProfileIndex = [_self currentProfileIndex];
869
870        if (returnCode != NSOKButton) {
871            [_profilePopup selectItemAtIndex:activeProfileIndex];
872            return;
873        }
874
875        // Popup panel does not contain the "Default" entry
876        selectedIndex++;
877
878        NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
879        /* remove selected profile from settings */
880        NSMutableArray *workArray = [[NSMutableArray alloc] initWithArray: [defaults objectForKey:@"VideoEffectProfiles"]];
881        [workArray removeObjectAtIndex:selectedIndex];
882        [defaults setObject:[NSArray arrayWithArray:workArray] forKey:@"VideoEffectProfiles"];
883
884        workArray = [[NSMutableArray alloc] initWithArray: [defaults objectForKey:@"VideoEffectProfileNames"]];
885        [workArray removeObjectAtIndex:selectedIndex];
886        [defaults setObject:[NSArray arrayWithArray:workArray] forKey:@"VideoEffectProfileNames"];
887
888        if (activeProfileIndex >= selectedIndex)
889            [self saveCurrentProfileIndex:(activeProfileIndex - 1)];
890
891        /* save defaults */
892        [defaults synchronize];
893
894        /* refresh UI */
895        [_self resetProfileSelector];
896    }];
897}
898
899- (IBAction)applyProfileCheckboxChanged:(id)sender
900{
901    [[NSUserDefaults standardUserDefaults] setBool:[sender state] forKey:@"VideoEffectApplyProfileOnStartup"];
902}
903
904#pragma mark -
905#pragma mark basic
906- (IBAction)enableAdjust:(id)sender
907{
908    if (sender == _adjustResetButton) {
909        [_adjustBrightnessSlider setFloatValue: 1.0];
910        [_adjustContrastSlider setFloatValue: 1.0];
911        [_adjustGammaSlider setFloatValue: 1.0];
912        [_adjustHueSlider setFloatValue: 0];
913        [_adjustSaturationSlider setFloatValue: 1.0];
914        [_adjustBrightnessSlider setToolTip: [NSString stringWithFormat:@"%0.3f", 1.0]];
915        [_adjustContrastSlider setToolTip: [NSString stringWithFormat:@"%0.3f", 1.0]];
916        [_adjustGammaSlider setToolTip: [NSString stringWithFormat:@"%0.3f", 1.0]];
917        [_adjustHueSlider setToolTip: [NSString stringWithFormat:@"%.0f", 0.0]];
918        [_adjustSaturationSlider setToolTip: [NSString stringWithFormat:@"%0.3f", 1.0]];
919
920        VLCCoreInteraction *vci_si = [VLCCoreInteraction sharedInstance];
921        [vci_si setVideoFilterProperty: "brightness" forFilter: "adjust" withValue: (vlc_value_t){ .f_float = 1.f }];
922        [vci_si setVideoFilterProperty: "contrast" forFilter: "adjust" withValue: (vlc_value_t){ .f_float = 1.f }];
923        [vci_si setVideoFilterProperty: "gamma" forFilter: "adjust" withValue: (vlc_value_t){ .f_float = 1.f }];
924        [vci_si setVideoFilterProperty: "hue" forFilter: "adjust" withValue: (vlc_value_t){ .f_float = .0f }];
925        [vci_si setVideoFilterProperty: "saturation" forFilter: "adjust" withValue: (vlc_value_t){ .f_float = 1.f }];
926    } else {
927        BOOL b_state = [_adjustCheckbox state];
928
929        [[VLCCoreInteraction sharedInstance] setVideoFilter: "adjust" on: b_state];
930        [_adjustBrightnessSlider setEnabled: b_state];
931        [_adjustBrightnessCheckbox setEnabled: b_state];
932        [_adjustBrightnessLabel setEnabled: b_state];
933        [_adjustContrastSlider setEnabled: b_state];
934        [_adjustContrastLabel setEnabled: b_state];
935        [_adjustGammaSlider setEnabled: b_state];
936        [_adjustGammaLabel setEnabled: b_state];
937        [_adjustHueSlider setEnabled: b_state];
938        [_adjustHueLabel setEnabled: b_state];
939        [_adjustSaturationSlider setEnabled: b_state];
940        [_adjustSaturationLabel setEnabled: b_state];
941        [_adjustResetButton setEnabled: b_state];
942    }
943}
944
945- (IBAction)adjustSliderChanged:(id)sender
946{
947    char const *psz_property = nil;
948    if (sender == _adjustBrightnessSlider)
949        psz_property = "brightness";
950    else if (sender == _adjustContrastSlider)
951        psz_property = "contrast";
952    else if (sender == _adjustGammaSlider)
953        psz_property = "gamma";
954    else if (sender == _adjustHueSlider)
955        psz_property = "hue";
956    else if (sender == _adjustSaturationSlider)
957        psz_property = "saturation";
958    assert(psz_property);
959
960    [[VLCCoreInteraction sharedInstance] setVideoFilterProperty: psz_property forFilter: "adjust" withValue: getWidgetFloatValue(sender)];
961
962    if (sender == _adjustHueSlider)
963        [_adjustHueSlider setToolTip: [NSString stringWithFormat:@"%.0f", [_adjustHueSlider floatValue]]];
964    else
965        [sender setToolTip: [NSString stringWithFormat:@"%0.3f", [sender floatValue]]];
966}
967
968- (IBAction)enableAdjustBrightnessThreshold:(id)sender
969{
970    [[VLCCoreInteraction sharedInstance] setVideoFilterProperty: "brightness-threshold"
971                                                      forFilter: "adjust"
972                                                      withValue: getWidgetBoolValue(sender)];
973}
974
975- (IBAction)enableSharpen:(id)sender
976{
977    BOOL b_state = [_sharpenCheckbox state];
978
979    [[VLCCoreInteraction sharedInstance] setVideoFilter: "sharpen" on: b_state];
980    [_sharpenSlider setEnabled: b_state];
981    [_sharpenLabel setEnabled: b_state];
982}
983
984- (IBAction)sharpenSliderChanged:(id)sender
985{
986    [[VLCCoreInteraction sharedInstance] setVideoFilterProperty: "sharpen-sigma" forFilter: "sharpen" withValue: getWidgetFloatValue(sender)];
987    [sender setToolTip: [NSString stringWithFormat:@"%0.3f", [sender floatValue]]];
988}
989
990- (IBAction)enableBanding:(id)sender
991{
992    BOOL b_state = [_bandingCheckbox state];
993
994    [[VLCCoreInteraction sharedInstance] setVideoFilter: "gradfun" on: b_state];
995    [_bandingSlider setEnabled: b_state];
996    [_bandingLabel setEnabled: b_state];
997}
998
999- (IBAction)bandingSliderChanged:(id)sender
1000{
1001    [[VLCCoreInteraction sharedInstance] setVideoFilterProperty: "gradfun-radius" forFilter: "gradfun" withValue: getWidgetIntValue(sender)];
1002    [sender setToolTip: [NSString stringWithFormat:@"%i", [sender intValue]]];
1003}
1004
1005- (IBAction)enableGrain:(id)sender
1006{
1007    BOOL b_state = [_grainCheckbox state];
1008
1009    [[VLCCoreInteraction sharedInstance] setVideoFilter: "grain" on: b_state];
1010    [_grainSlider setEnabled: b_state];
1011    [_grainLabel setEnabled: b_state];
1012}
1013
1014- (IBAction)grainSliderChanged:(id)sender
1015{
1016    [[VLCCoreInteraction sharedInstance] setVideoFilterProperty: "grain-variance" forFilter: "grain" withValue: getWidgetFloatValue(sender)];
1017    [sender setToolTip: [NSString stringWithFormat:@"%0.3f", [sender floatValue]]];
1018}
1019
1020
1021#pragma mark -
1022#pragma mark crop
1023
1024- (IBAction)cropObjectChanged:(id)sender
1025{
1026    if ([_cropSyncTopBottomCheckbox state]) {
1027        if (sender == _cropBottomTextField || sender == _cropBottomStepper)
1028            [self setCropTopValue: [self cropBottomValue]];
1029        else
1030            [self setCropBottomValue: [self cropTopValue]];
1031    }
1032    if ([_cropSyncLeftRightCheckbox state]) {
1033        if (sender == _cropRightTextField || sender == _cropRightStepper)
1034            [self setCropLeftValue: [self cropRightValue]];
1035        else
1036            [self setCropRightValue: [self cropLeftValue]];
1037    }
1038
1039    NSArray<NSValue *> *vouts = getVouts();
1040    if (vouts)
1041        for (NSValue *ptr in vouts) {
1042            vout_thread_t *p_vout = [ptr pointerValue];
1043            var_SetInteger(p_vout, "crop-top", [_cropTopTextField intValue]);
1044            var_SetInteger(p_vout, "crop-bottom", [_cropBottomTextField intValue]);
1045            var_SetInteger(p_vout, "crop-left", [_cropLeftTextField intValue]);
1046            var_SetInteger(p_vout, "crop-right", [_cropRightTextField intValue]);
1047            vlc_object_release(p_vout);
1048        }
1049}
1050
1051#pragma mark -
1052#pragma mark geometry
1053- (IBAction)enableTransform:(id)sender
1054{
1055    [[VLCCoreInteraction sharedInstance] setVideoFilter: "transform" on: [_transformCheckbox state]];
1056    [_transformPopup setEnabled: [_transformCheckbox state]];
1057}
1058
1059- (IBAction)transformModifierChanged:(id)sender
1060{
1061    vlc_value_t value = { .psz_string = (char *)[[[_transformPopup selectedItem] representedObject] UTF8String] };
1062    [[VLCCoreInteraction sharedInstance] setVideoFilterProperty: "transform-type" forFilter: "transform" withValue: value];
1063}
1064
1065- (IBAction)enableZoom:(id)sender
1066{
1067    [[VLCCoreInteraction sharedInstance] setVideoFilter: "magnify" on: [_zoomCheckbox state]];
1068}
1069
1070- (IBAction)enablePuzzle:(id)sender
1071{
1072    BOOL b_state = [_puzzleCheckbox state];
1073
1074    [[VLCCoreInteraction sharedInstance] setVideoFilter: "puzzle" on: b_state];
1075    [_puzzleColumnsTextField setEnabled: b_state];
1076    [_puzzleColumnsStepper setEnabled: b_state];
1077    [_puzzleColumnsLabel setEnabled: b_state];
1078    [_puzzleRowsTextField setEnabled: b_state];
1079    [_puzzleRowsStepper setEnabled: b_state];
1080    [_puzzleRowsLabel setEnabled: b_state];
1081}
1082
1083- (IBAction)puzzleModifierChanged:(id)sender
1084{
1085    if (sender == _puzzleColumnsTextField || sender == _puzzleColumnsStepper)
1086        [[VLCCoreInteraction sharedInstance] setVideoFilterProperty: "puzzle-cols" forFilter: "puzzle" withValue: getWidgetIntValue(sender)];
1087    else
1088        [[VLCCoreInteraction sharedInstance] setVideoFilterProperty: "puzzle-rows" forFilter: "puzzle" withValue: getWidgetIntValue(sender)];
1089}
1090
1091- (IBAction)enableClone:(id)sender
1092{
1093    BOOL b_state = [_cloneCheckbox state];
1094
1095    if (b_state && [_wallCheckbox state]) {
1096        [_wallCheckbox setState: NSOffState];
1097        [self enableWall:_wallCheckbox];
1098    }
1099
1100    [[VLCCoreInteraction sharedInstance] setVideoFilter: "clone" on: b_state];
1101    [_cloneNumberLabel setEnabled: b_state];
1102    [_cloneNumberTextField setEnabled: b_state];
1103    [_cloneNumberStepper setEnabled: b_state];
1104}
1105
1106- (IBAction)cloneModifierChanged:(id)sender
1107{
1108    [[VLCCoreInteraction sharedInstance] setVideoFilterProperty: "clone-count" forFilter: "clone" withValue: getWidgetIntValue(_cloneNumberTextField)];
1109}
1110
1111- (IBAction)enableWall:(id)sender
1112{
1113    BOOL b_state = [_wallCheckbox state];
1114
1115    if (b_state && [_cloneCheckbox state]) {
1116        [_cloneCheckbox setState: NSOffState];
1117        [self enableClone:_cloneCheckbox];
1118    }
1119
1120    [[VLCCoreInteraction sharedInstance] setVideoFilter: "wall" on: b_state];
1121    [_wallNumberOfColumnsTextField setEnabled: b_state];
1122    [_wallNumberOfColumnsStepper setEnabled: b_state];
1123    [_wallNumberOfColumnsLabel setEnabled: b_state];
1124
1125    [_wallNumbersOfRowsTextField setEnabled: b_state];
1126    [_wallNumbersOfRowsStepper setEnabled: b_state];
1127    [_wallNumbersOfRowsLabel setEnabled: b_state];
1128}
1129
1130- (IBAction)wallModifierChanged:(id)sender
1131{
1132    char const *psz_property;
1133    if (sender == _wallNumberOfColumnsTextField || sender == _wallNumberOfColumnsStepper)
1134        psz_property = "wall-cols";
1135    else
1136        psz_property = "wall-rows";
1137    [[VLCCoreInteraction sharedInstance] setVideoFilterProperty: psz_property forFilter: "wall" withValue: getWidgetIntValue(sender)];
1138}
1139
1140#pragma mark -
1141#pragma mark color
1142- (IBAction)enableThreshold:(id)sender
1143{
1144    BOOL b_state = [_thresholdCheckbox state];
1145
1146    [[VLCCoreInteraction sharedInstance] setVideoFilter: "colorthres" on: b_state];
1147    [_thresholdColorTextField setEnabled: b_state];
1148    [_thresholdColorLabel setEnabled: b_state];
1149    [_thresholdSaturationSlider setEnabled: b_state];
1150    [_thresholdSaturationLabel setEnabled: b_state];
1151    [_thresholdSimilaritySlider setEnabled: b_state];
1152    [_thresholdSimilarityLabel setEnabled: b_state];
1153}
1154
1155- (IBAction)thresholdModifierChanged:(id)sender
1156{
1157    if (sender == _thresholdColorTextField)
1158        [[VLCCoreInteraction sharedInstance] setVideoFilterProperty: "colorthres-color" forFilter: "colorthres" withValue: getWidgetIntValue(sender)];
1159    else if (sender == _thresholdSaturationSlider) {
1160        [[VLCCoreInteraction sharedInstance] setVideoFilterProperty: "colorthres-saturationthres" forFilter: "colorthres" withValue: getWidgetIntValue(sender)];
1161        [_thresholdSaturationSlider setToolTip: [NSString stringWithFormat:@"%i", [_thresholdSaturationSlider intValue]]];
1162    } else {
1163        [[VLCCoreInteraction sharedInstance] setVideoFilterProperty: "colorthres-similaritythres" forFilter: "colorthres" withValue: getWidgetIntValue(sender)];
1164        [_thresholdSimilaritySlider setToolTip: [NSString stringWithFormat:@"%i", [_thresholdSimilaritySlider intValue]]];
1165    }
1166}
1167
1168- (IBAction)enableSepia:(id)sender
1169{
1170    BOOL b_state = [_sepiaCheckbox state];
1171
1172    [[VLCCoreInteraction sharedInstance] setVideoFilter: "sepia" on: b_state];
1173    [_sepiaTextField setEnabled: b_state];
1174    [_sepiaStepper setEnabled: b_state];
1175    [_sepiaLabel setEnabled: b_state];
1176}
1177
1178- (IBAction)sepiaModifierChanged:(id)sender
1179{
1180    [[VLCCoreInteraction sharedInstance] setVideoFilterProperty: "sepia-intensity" forFilter: "sepia" withValue: getWidgetIntValue(sender)];
1181}
1182
1183- (IBAction)enableGradient:(id)sender
1184{
1185    BOOL b_state = [_gradientCheckbox state];
1186
1187    [[VLCCoreInteraction sharedInstance] setVideoFilter: "gradient" on: b_state];
1188    [_gradientModePopup setEnabled: b_state];
1189    [_gradientModeLabel setEnabled: b_state];
1190    [_gradientColorCheckbox setEnabled: b_state];
1191    [_gradientCartoonCheckbox setEnabled: b_state];
1192}
1193
1194- (IBAction)gradientModifierChanged:(id)sender
1195{
1196    if (sender == _gradientModePopup) {
1197        vlc_value_t value = { .psz_string = (char *)[[[sender selectedItem] representedObject] UTF8String] };
1198        [[VLCCoreInteraction sharedInstance] setVideoFilterProperty: "gradient-mode" forFilter: "gradient" withValue: value];
1199    } else if (sender == _gradientColorCheckbox)
1200        [[VLCCoreInteraction sharedInstance] setVideoFilterProperty: "gradient-type" forFilter: "gradient" withValue: getWidgetBoolValue(sender)];
1201    else
1202        [[VLCCoreInteraction sharedInstance] setVideoFilterProperty: "gradient-cartoon" forFilter: "gradient" withValue: getWidgetBoolValue(sender)];
1203}
1204
1205- (IBAction)enableExtract:(id)sender
1206{
1207    BOOL b_state = [_extractCheckbox state];
1208    [[VLCCoreInteraction sharedInstance] setVideoFilter: "extract" on: b_state];
1209    [_extractTextField setEnabled: b_state];
1210    [_extractLabel setEnabled: b_state];
1211}
1212
1213- (IBAction)extractModifierChanged:(id)sender
1214{
1215    [[VLCCoreInteraction sharedInstance] setVideoFilterProperty: "extract-component" forFilter: "extract" withValue: getWidgetIntValue(sender)];
1216}
1217
1218- (IBAction)enableInvert:(id)sender
1219{
1220    [[VLCCoreInteraction sharedInstance] setVideoFilter: "invert" on: [_invertCheckbox state]];
1221}
1222
1223- (IBAction)enablePosterize:(id)sender
1224{
1225    BOOL b_state = [_posterizeCheckbox state];
1226
1227    [[VLCCoreInteraction sharedInstance] setVideoFilter: "posterize" on: b_state];
1228    [_posterizeTextField setEnabled: b_state];
1229    [_posterizeStepper setEnabled: b_state];
1230    [_posterizeLabel setEnabled: b_state];
1231}
1232
1233- (IBAction)posterizeModifierChanged:(id)sender
1234{
1235    [[VLCCoreInteraction sharedInstance] setVideoFilterProperty: "posterize-level" forFilter: "posterize" withValue: getWidgetIntValue(sender)];
1236}
1237
1238- (IBAction)enableBlur:(id)sender
1239{
1240    BOOL b_state = [_blurCheckbox state];
1241
1242    [[VLCCoreInteraction sharedInstance] setVideoFilter: "motionblur" on: b_state];
1243    [_blurSlider setEnabled: b_state];
1244    [_blurLabel setEnabled: b_state];
1245}
1246
1247- (IBAction)blurModifierChanged:(id)sender
1248{
1249    [[VLCCoreInteraction sharedInstance] setVideoFilterProperty: "blur-factor" forFilter: "motionblur" withValue: getWidgetIntValue(sender)];
1250    [sender setToolTip: [NSString stringWithFormat:@"%i", [sender intValue]]];
1251}
1252
1253- (IBAction)enableMotionDetect:(id)sender
1254{
1255    [[VLCCoreInteraction sharedInstance] setVideoFilter: "motiondetect" on: [_motiondetectCheckbox state]];
1256}
1257
1258- (IBAction)enableWaterEffect:(id)sender
1259{
1260    [[VLCCoreInteraction sharedInstance] setVideoFilter: "ripple" on: [_watereffectCheckbox state]];
1261}
1262
1263- (IBAction)enableWaves:(id)sender
1264{
1265    [[VLCCoreInteraction sharedInstance] setVideoFilter: "wave" on: [_wavesCheckbox state]];
1266}
1267
1268- (IBAction)enablePsychedelic:(id)sender
1269{
1270    [[VLCCoreInteraction sharedInstance] setVideoFilter: "psychedelic" on: [_psychedelicCheckbox state]];
1271}
1272
1273#pragma mark -
1274#pragma mark Miscellaneous
1275- (IBAction)enableAddText:(id)sender
1276{
1277    BOOL b_state = [_addTextCheckbox state];
1278    VLCCoreInteraction *vci_si = [VLCCoreInteraction sharedInstance];
1279
1280    [_addTextPositionPopup setEnabled: b_state];
1281    [_addTextPositionLabel setEnabled: b_state];
1282    [_addTextTextLabel setEnabled: b_state];
1283    [_addTextTextTextField setEnabled: b_state];
1284    [vci_si setVideoFilter: "marq" on: b_state];
1285    [vci_si setVideoFilterProperty: "marq-marquee" forFilter: "marq" withValue: getWidgetStringValue(_addTextTextTextField)];
1286    [vci_si setVideoFilterProperty: "marq-position" forFilter: "marq" withValue: (vlc_value_t){ .i_int = [[_addTextPositionPopup selectedItem] tag] }];
1287}
1288
1289- (IBAction)addTextModifierChanged:(id)sender
1290{
1291    if (sender == _addTextTextTextField)
1292        [[VLCCoreInteraction sharedInstance] setVideoFilterProperty: "marq-marquee" forFilter: "marq" withValue: getWidgetStringValue(sender)];
1293    else
1294        [[VLCCoreInteraction sharedInstance] setVideoFilterProperty: "marq-position" forFilter: "marq" withValue: (vlc_value_t){ .i_int = [[sender selectedItem] tag] }];
1295}
1296
1297- (IBAction)enableAddLogo:(id)sender
1298{
1299    BOOL b_state = [_addLogoCheckbox state];
1300
1301    [_addLogoPositionPopup setEnabled: b_state];
1302    [_addLogoPositionLabel setEnabled: b_state];
1303    [_addLogoLogoTextField setEnabled: b_state];
1304    [_addLogoLogoLabel setEnabled: b_state];
1305    [_addLogoTransparencySlider setEnabled: b_state];
1306    [_addLogoTransparencyLabel setEnabled: b_state];
1307    [[VLCCoreInteraction sharedInstance] setVideoFilter: "logo" on: b_state];
1308}
1309
1310- (IBAction)addLogoModifierChanged:(id)sender
1311{
1312    if (sender == _addLogoLogoTextField)
1313        [[VLCCoreInteraction sharedInstance] setVideoFilterProperty: "logo-file" forFilter: "logo" withValue: getWidgetStringValue(sender)];
1314    else if (sender == _addLogoPositionPopup)
1315        [[VLCCoreInteraction sharedInstance] setVideoFilterProperty: "logo-position" forFilter: "logo" withValue: (vlc_value_t){ .i_int = [[_addLogoPositionPopup selectedItem] tag] }];
1316    else {
1317        [[VLCCoreInteraction sharedInstance] setVideoFilterProperty: "logo-opacity" forFilter: "logo" withValue: getWidgetIntValue(sender)];
1318        [_addLogoTransparencySlider setToolTip: [NSString stringWithFormat:@"%i", [_addLogoTransparencySlider intValue]]];
1319    }
1320}
1321
1322- (IBAction)enableAnaglyph:(id)sender
1323{
1324    [[VLCCoreInteraction sharedInstance] setVideoFilter: "anaglyph" on: [_anaglyphCheckbox state]];
1325}
1326
1327@end
1328