VLCVideoEffectsWindowController.m 57.8 KB
Newer Older
1
/*****************************************************************************
2
 * VLCVideoEffectsWindowController.m: MacOS X interface module
3
 *****************************************************************************
4
 * Copyright (C) 2011-2015 Felix Paul Kühne
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
 * $Id$
 *
 * Authors: Felix Paul Kühne <fkuehne -at- videolan -dot- org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 *****************************************************************************/

24
#import "CompatibilityFixes.h"
25
#import "VLCMain.h"
26
#import "VLCVideoEffectsWindowController.h"
27 28
#import "VLCPopupPanelController.h"
#import "VLCTextfieldPanelController.h"
29
#import "VLCCoreInteraction.h"
30
#import "VLCHexNumberFormatter.h"
31

32 33 34 35 36
#define getWidgetBoolValue(w)   ((vlc_value_t){ .b_bool = [w state] })
#define getWidgetIntValue(w)    ((vlc_value_t){ .i_int = [w intValue] })
#define getWidgetFloatValue(w)  ((vlc_value_t){ .f_float = [w floatValue] })
#define getWidgetStringValue(w) ((vlc_value_t){ .psz_string = (char *)[[w stringValue] UTF8String] })

37

38
#pragma mark -
39
#pragma mark Initialization
40

41
@implementation VLCVideoEffectsWindowController
42

43 44
+ (void)initialize
{
45 46 47 48 49 50 51
    /*
     * Video effects profiles starting with 3.0:
     * - Index 0 is assumed to be the default profile from previous versions
     * - Index 0 from settings is never read or written anymore starting with 3.0, as the Default profile
     *   is not persisted anymore.
     */

52 53 54 55
    NSDictionary *appDefaults = [NSDictionary dictionaryWithObjectsAndKeys:
                                 [NSArray arrayWithObject:[VLCVideoEffectsWindowController defaultProfileString]], @"VideoEffectProfiles",
                                 [NSArray arrayWithObject:_NS("Default")], @"VideoEffectProfileNames",
                                 nil];
56 57 58
    [[NSUserDefaults standardUserDefaults] registerDefaults:appDefaults];
}

59 60 61 62 63
+ (NSString *)defaultProfileString
{
    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";
}

64 65
- (id)init
{
66 67
    self = [super initWithWindowNibName:@"VideoEffects"];
    if (self) {
68 69
        self.popupPanel = [[VLCPopupPanelController alloc] init];
        self.textfieldPanel = [[VLCTextfieldPanelController alloc] init];
70

71 72 73 74 75 76 77 78 79
        NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
        if ([defaults boolForKey:@"VideoEffectApplyProfileOnStartup"]) {
            // This does not reset the UI (which does not exist yet), but it initalizes needed playlist vars
            [self resetValues];

            [self loadProfile];
        } else {
            [self saveCurrentProfileIndex:0];
        }
80

81 82
    }

83
    return self;
84 85
}

86
/// Loads values from profile into variables
87 88 89 90 91 92
- (void)loadProfile
{
    intf_thread_t *p_intf = getIntf();
    playlist_t *p_playlist = pl_Get(p_intf);
    VLCCoreInteraction *vci_si = [VLCCoreInteraction sharedInstance];
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
93
    NSInteger profileIndex = [self currentProfileIndex];
94

95 96 97 98 99 100 101
    NSString *profileString;
    if (profileIndex == 0)
        profileString = [VLCVideoEffectsWindowController defaultProfileString];
    else
        profileString = [[defaults objectForKey:@"VideoEffectProfiles"] objectAtIndex:profileIndex];

    NSArray *items = [profileString componentsSeparatedByString:@";"];
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185

    // version 1 of profile string has 32 entries
    if ([items count] < 32) {
        msg_Err(p_intf, "Error in parsing profile string");
        return;
    }

    /* filter handling */
    NSString *tempString = B64DecNSStr([items firstObject]);
    NSArray<NSValue *> *vouts = getVouts();

    /* enable the new filters */
    var_SetString(p_playlist, "video-filter", [tempString UTF8String]);
    if (vouts)
        for (NSValue *ptr in vouts) {
            vout_thread_t *p_vout = [ptr pointerValue];
            var_SetString(p_vout, "video-filter", [tempString UTF8String]);
        }

    tempString = B64DecNSStr([items objectAtIndex:1]);
    /* enable another round of new filters */
    var_SetString(p_playlist, "sub-source", [tempString UTF8String]);
    if (vouts)
        for (NSValue *ptr in vouts) {
            vout_thread_t *p_vout = [ptr pointerValue];
            var_SetString(p_vout, "sub-source", [tempString UTF8String]);
            vlc_object_release(p_vout);
        }

    tempString = B64DecNSStr([items objectAtIndex:2]);
    /* enable another round of new filters */
    char *psz_current_splitter = var_GetString(p_playlist, "video-splitter");
    bool b_filter_changed = ![tempString isEqualToString:toNSStr(psz_current_splitter)];
    free(psz_current_splitter);

    if (b_filter_changed)
        var_SetString(p_playlist, "video-splitter", [tempString UTF8String]);

    /* try to set filter values on-the-fly and store them appropriately */
    // index 3 is deprecated
    [vci_si setVideoFilterProperty: "contrast" forFilter: "adjust" withValue: getWidgetFloatValue([items objectAtIndex:4])];
    [vci_si setVideoFilterProperty: "brightness" forFilter: "adjust" withValue: getWidgetFloatValue([items objectAtIndex:5])];
    [vci_si setVideoFilterProperty: "saturation" forFilter: "adjust" withValue: getWidgetFloatValue([items objectAtIndex:6])];
    [vci_si setVideoFilterProperty: "gamma" forFilter: "adjust" withValue: getWidgetFloatValue([items objectAtIndex:7])];
    [vci_si setVideoFilterProperty: "sharpen-sigma" forFilter: "sharpen" withValue: getWidgetFloatValue([items objectAtIndex:8])];
    [vci_si setVideoFilterProperty: "gradfun-radius" forFilter: "gradfun" withValue: getWidgetIntValue([items objectAtIndex:9])];
    [vci_si setVideoFilterProperty: "grain-variance" forFilter: "grain" withValue: getWidgetFloatValue([items objectAtIndex:10])];
    [vci_si setVideoFilterProperty: "transform-type" forFilter: "transform" withValue: (vlc_value_t){ .psz_string = (char *)[B64DecNSStr([items objectAtIndex:11]) UTF8String] }];
    [vci_si setVideoFilterProperty: "puzzle-rows" forFilter: "puzzle" withValue: getWidgetIntValue([items objectAtIndex:12])];
    [vci_si setVideoFilterProperty: "puzzle-cols" forFilter: "puzzle" withValue: getWidgetIntValue([items objectAtIndex:13])];
    [vci_si setVideoFilterProperty: "colorthres-color" forFilter: "colorthres" withValue: getWidgetIntValue([items objectAtIndex:14])];
    [vci_si setVideoFilterProperty: "colorthres-saturationthres" forFilter: "colorthres" withValue: getWidgetIntValue([items objectAtIndex:15])];
    [vci_si setVideoFilterProperty: "colorthres-similaritythres" forFilter: "colorthres" withValue: getWidgetIntValue([items objectAtIndex:16])];
    [vci_si setVideoFilterProperty: "sepia-intensity" forFilter: "sepia" withValue: getWidgetIntValue([items objectAtIndex:17])];
    [vci_si setVideoFilterProperty: "gradient-mode" forFilter: "gradient" withValue: (vlc_value_t){ .psz_string = (char *)[B64DecNSStr([items objectAtIndex:18]) UTF8String] }];
    [vci_si setVideoFilterProperty: "gradient-cartoon" forFilter: "gradient" withValue: (vlc_value_t){ .b_bool = [[items objectAtIndex:19] intValue] }];
    [vci_si setVideoFilterProperty: "gradient-type" forFilter: "gradient" withValue: getWidgetIntValue([items objectAtIndex:20])];
    [vci_si setVideoFilterProperty: "extract-component" forFilter: "extract" withValue: getWidgetIntValue([items objectAtIndex:21])];
    [vci_si setVideoFilterProperty: "posterize-level" forFilter: "posterize" withValue: getWidgetIntValue([items objectAtIndex:22])];
    [vci_si setVideoFilterProperty: "blur-factor" forFilter: "motionblur" withValue: getWidgetIntValue([items objectAtIndex:23])];
    [vci_si setVideoFilterProperty: "marq-marquee" forFilter: "marq" withValue: (vlc_value_t){ .psz_string = (char *)[B64DecNSStr([items objectAtIndex:24]) UTF8String] }];
    [vci_si setVideoFilterProperty: "marq-position" forFilter: "marq" withValue: getWidgetIntValue([items objectAtIndex:25])];
    [vci_si setVideoFilterProperty: "logo-file" forFilter: "logo" withValue: (vlc_value_t){ .psz_string = (char *)[B64DecNSStr([items objectAtIndex:26]) UTF8String] }];
    [vci_si setVideoFilterProperty: "logo-position" forFilter: "logo" withValue: getWidgetIntValue([items objectAtIndex:27])];
    [vci_si setVideoFilterProperty: "logo-opacity" forFilter: "logo" withValue: getWidgetIntValue([items objectAtIndex:28])];
    [vci_si setVideoFilterProperty: "clone-count" forFilter: "clone" withValue: getWidgetIntValue([items objectAtIndex:29])];
    [vci_si setVideoFilterProperty: "wall-rows" forFilter: "wall" withValue: getWidgetIntValue([items objectAtIndex:30])];
    [vci_si setVideoFilterProperty: "wall-cols" forFilter: "wall" withValue: getWidgetIntValue([items objectAtIndex:31])];

    if ([items count] >= 33) { // version >=2 of profile string
        [vci_si setVideoFilterProperty: "brightness-threshold" forFilter: "adjust" withValue: (vlc_value_t){ .b_bool = [[items objectAtIndex:32] intValue] }];
    }

    vlc_value_t hueValue;
    if ([items count] >= 34) { // version >=3 of profile string
        hueValue.f_float = [[items objectAtIndex:33] floatValue];
    } else {
        hueValue.f_float = [[items objectAtIndex:3] intValue]; // deprecated since 3.0.0
        // convert to new scale ([0,360] --> [-180,180])
        hueValue.f_float -= 180;
    }
    [vci_si setVideoFilterProperty: "hue" forFilter: "adjust" withValue: hueValue];
}

186
- (void)windowDidLoad
187
{
188 189 190
    [self.window setTitle: _NS("Video Effects")];
    [self.window setExcludedFromWindowsMenu:YES];
    [self.window setCollectionBehavior: NSWindowCollectionBehaviorFullScreenAuxiliary];
191

192 193 194 195 196
    [_segmentView setLabel:_NS("Basic") forSegment:0];
    [_segmentView setLabel:_NS("Crop") forSegment:1];
    [_segmentView setLabel:_NS("Geometry") forSegment:2];
    [_segmentView setLabel:_NS("Color") forSegment:3];
    [_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 202
    [self resetProfileSelector];

203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226
    [_adjustCheckbox setTitle:_NS("Image Adjust")];
    [_adjustHueLabel setStringValue:_NS("Hue")];
    [_adjustContrastLabel setStringValue:_NS("Contrast")];
    [_adjustBrightnessLabel setStringValue:_NS("Brightness")];
    [_adjustBrightnessCheckbox setTitle:_NS("Brightness Threshold")];
    [_adjustSaturationLabel setStringValue:_NS("Saturation")];
    [_adjustGammaLabel setStringValue:_NS("Gamma")];
    [_adjustResetButton setTitle: _NS("Reset")];
    [_sharpenCheckbox setTitle:_NS("Sharpen")];
    [_sharpenLabel setStringValue:_NS("Sigma")];
    [_bandingCheckbox setTitle:_NS("Banding removal")];
    [_bandingLabel setStringValue:_NS("Radius")];
    [_grainCheckbox setTitle:_NS("Film Grain")];
    [_grainLabel setStringValue:_NS("Variance")];
    [_cropTopLabel setStringValue:_NS("Top")];
    [_cropLeftLabel setStringValue:_NS("Left")];
    [_cropRightLabel setStringValue:_NS("Right")];
    [_cropBottomLabel setStringValue:_NS("Bottom")];
    [_cropSyncTopBottomCheckbox setTitle:_NS("Synchronize top and bottom")];
    [_cropSyncLeftRightCheckbox setTitle:_NS("Synchronize left and right")];

    [_transformCheckbox setTitle:_NS("Transform")];
    [_transformPopup removeAllItems];
    [_transformPopup addItemWithTitle: _NS("Rotate by 90 degrees")];
227
    [[_transformPopup lastItem] setRepresentedObject: @"90"];
228 229
    [[_transformPopup lastItem] setTag: 90];
    [_transformPopup addItemWithTitle: _NS("Rotate by 180 degrees")];
230
    [[_transformPopup lastItem] setRepresentedObject: @"180"];
231 232
    [[_transformPopup lastItem] setTag: 180];
    [_transformPopup addItemWithTitle: _NS("Rotate by 270 degrees")];
233
    [[_transformPopup lastItem] setRepresentedObject: @"270"];
234 235
    [[_transformPopup lastItem] setTag: 270];
    [_transformPopup addItemWithTitle: _NS("Flip horizontally")];
236
    [[_transformPopup lastItem] setRepresentedObject: @"hflip"];
237 238
    [[_transformPopup lastItem] setTag: 1];
    [_transformPopup addItemWithTitle: _NS("Flip vertically")];
239
    [[_transformPopup lastItem] setRepresentedObject: @"vflip"];
240 241 242 243 244 245 246 247 248 249 250 251 252
    [[_transformPopup lastItem] setTag: 2];
    [_zoomCheckbox setTitle:_NS("Magnification/Zoom")];
    [_puzzleCheckbox setTitle:_NS("Puzzle game")];
    [_puzzleRowsLabel setStringValue:_NS("Rows")];
    [_puzzleColumnsLabel setStringValue:_NS("Columns")];
    [_cloneCheckbox setTitle:_NS("Clone")];
    [_cloneNumberLabel setStringValue:_NS("Number of clones")];
    [_wallCheckbox setTitle:_NS("Wall")];
    [_wallNumbersOfRowsLabel setStringValue:_NS("Rows")];
    [_wallNumberOfColumnsLabel setStringValue:_NS("Columns")];

    [_thresholdCheckbox setTitle:_NS("Color threshold")];
    [_thresholdColorLabel setStringValue:_NS("Color")];
253
    [_thresholdColorTextField setFormatter:[[VLCHexNumberFormatter alloc] init]];
254 255 256 257 258 259 260 261
    [_thresholdSaturationLabel setStringValue:_NS("Saturation")];
    [_thresholdSimilarityLabel setStringValue:_NS("Similarity")];
    [_sepiaCheckbox setTitle:_NS("Sepia")];
    [_sepiaLabel setStringValue:_NS("Intensity")];
    [_gradientCheckbox setTitle:_NS("Gradient")];
    [_gradientModeLabel setStringValue:_NS("Mode")];
    [_gradientModePopup removeAllItems];
    [_gradientModePopup addItemWithTitle: _NS("Gradient")];
262
    [[_gradientModePopup lastItem] setRepresentedObject: @"gradient"];
263 264
    [[_gradientModePopup lastItem] setTag: 1];
    [_gradientModePopup addItemWithTitle: _NS("Edge")];
265
    [[_gradientModePopup lastItem] setRepresentedObject: @"edge"];
266 267
    [[_gradientModePopup lastItem] setTag: 2];
    [_gradientModePopup addItemWithTitle: _NS("Hough")];
268
    [[_gradientModePopup lastItem] setRepresentedObject: @"hough"];
269 270 271 272 273
    [[_gradientModePopup lastItem] setTag: 3];
    [_gradientColorCheckbox setTitle:_NS("Color")];
    [_gradientCartoonCheckbox setTitle:_NS("Cartoon")];
    [_extractCheckbox setTitle:_NS("Color extraction")];
    [_extractLabel setStringValue:_NS("Color")];
274
    [_extractTextField setFormatter:[[VLCHexNumberFormatter alloc] init]];
275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330
    [_invertCheckbox setTitle:_NS("Invert colors")];
    [_posterizeCheckbox setTitle:_NS("Posterize")];
    [_posterizeLabel setStringValue:_NS("Posterize level")];
    [_blurCheckbox setTitle:_NS("Motion blur")];
    [_blurLabel setStringValue:_NS("Factor")];
    [_motiondetectCheckbox setTitle:_NS("Motion Detect")];
    [_watereffectCheckbox setTitle:_NS("Water effect")];
    [_wavesCheckbox setTitle:_NS("Waves")];
    [_psychedelicCheckbox setTitle:_NS("Psychedelic")];
    [_anaglyphCheckbox setTitle:_NS("Anaglyph")];
    [_addTextCheckbox setTitle:_NS("Add text")];
    [_addTextTextLabel setStringValue:_NS("Text")];
    [_addTextPositionLabel setStringValue:_NS("Position")];
    [_addTextPositionPopup removeAllItems];
    [_addTextPositionPopup addItemWithTitle: _NS("Center")];
    [[_addTextPositionPopup lastItem] setTag: 0];
    [_addTextPositionPopup addItemWithTitle: _NS("Left")];
    [[_addTextPositionPopup lastItem] setTag: 1];
    [_addTextPositionPopup addItemWithTitle: _NS("Right")];
    [[_addTextPositionPopup lastItem] setTag: 2];
    [_addTextPositionPopup addItemWithTitle: _NS("Top")];
    [[_addTextPositionPopup lastItem] setTag: 4];
    [_addTextPositionPopup addItemWithTitle: _NS("Bottom")];
    [[_addTextPositionPopup lastItem] setTag: 8];
    [_addTextPositionPopup addItemWithTitle: _NS("Top-Left")];
    [[_addTextPositionPopup lastItem] setTag: 5];
    [_addTextPositionPopup addItemWithTitle: _NS("Top-Right")];
    [[_addTextPositionPopup lastItem] setTag: 6];
    [_addTextPositionPopup addItemWithTitle: _NS("Bottom-Left")];
    [[_addTextPositionPopup lastItem] setTag: 9];
    [_addTextPositionPopup addItemWithTitle: _NS("Bottom-Right")];
    [[_addTextPositionPopup lastItem] setTag: 10];
    [_addLogoCheckbox setTitle:_NS("Add logo")];
    [_addLogoLogoLabel setStringValue:_NS("Logo")];
    [_addLogoPositionLabel setStringValue:_NS("Position")];
    [_addLogoPositionPopup removeAllItems];
    [_addLogoPositionPopup addItemWithTitle: _NS("Center")];
    [[_addLogoPositionPopup lastItem] setTag: 0];
    [_addLogoPositionPopup addItemWithTitle: _NS("Left")];
    [[_addLogoPositionPopup lastItem] setTag: 1];
    [_addLogoPositionPopup addItemWithTitle: _NS("Right")];
    [[_addLogoPositionPopup lastItem] setTag: 2];
    [_addLogoPositionPopup addItemWithTitle: _NS("Top")];
    [[_addLogoPositionPopup lastItem] setTag: 4];
    [_addLogoPositionPopup addItemWithTitle: _NS("Bottom")];
    [[_addLogoPositionPopup lastItem] setTag: 8];
    [_addLogoPositionPopup addItemWithTitle: _NS("Top-Left")];
    [[_addLogoPositionPopup lastItem] setTag: 5];
    [_addLogoPositionPopup addItemWithTitle: _NS("Top-Right")];
    [[_addLogoPositionPopup lastItem] setTag: 6];
    [_addLogoPositionPopup addItemWithTitle: _NS("Bottom-Left")];
    [[_addLogoPositionPopup lastItem] setTag: 9];
    [_addLogoPositionPopup addItemWithTitle: _NS("Bottom-Right")];
    [[_addLogoPositionPopup lastItem] setTag: 10];
    [_addLogoTransparencyLabel setStringValue:_NS("Transparency")];

331 332 333 334 335
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(inputChangedEvent:)
                                                 name:VLCInputChangedNotification
                                               object:nil];

336 337 338
    [self resetValues];
}

339 340 341 342 343
- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

344 345
- (void)updateCocoaWindowLevel:(NSInteger)i_level
{
346
    if (self.isWindowLoaded && [self.window isVisible] && [self.window level] != i_level)
347
        [self.window setLevel: i_level];
348 349
}

350 351
#pragma mark -
#pragma mark internal functions
352

353 354 355 356 357 358 359 360 361 362
- (void)saveCurrentProfileIndex:(NSInteger)index
{
    [[NSUserDefaults standardUserDefaults] setInteger:index forKey:@"VideoEffectSelectedProfile"];
}

- (NSInteger)currentProfileIndex
{
    return [[NSUserDefaults standardUserDefaults] integerForKey:@"VideoEffectSelectedProfile"];
}

363 364 365 366 367 368 369 370 371 372
/// Returns the list of profile names (omitting the Default entry)
- (NSArray *)nonDefaultProfileNames
{
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

    NSMutableArray *names = [[defaults stringArrayForKey:@"VideoEffectProfileNames"] mutableCopy];
    [names removeObjectAtIndex:0];
    return [names copy];
}

373 374 375 376 377 378 379 380 381
-(void)inputChangedEvent:(NSNotification *)o_notification
{
    // reset crop values when input changed
    [self setCropBottomValue:0];
    [self setCropTopValue:0];
    [self setCropLeftValue:0];
    [self setCropRightValue:0];
}

382 383
- (void)resetProfileSelector
{
384
    [_profilePopup removeAllItems];
385

386 387 388
    // Ignore "Default" index 0 from settings
    [_profilePopup addItemWithTitle:_NS("Default")];

389
    [_profilePopup addItemsWithTitles:[self nonDefaultProfileNames]];
390

391 392 393 394
    [[_profilePopup menu] addItem:[NSMenuItem separatorItem]];
    [_profilePopup addItemWithTitle:_NS("Duplicate current profile...")];
    [[_profilePopup lastItem] setTarget: self];
    [[_profilePopup lastItem] setAction: @selector(addProfile:)];
395

396
    if ([[self nonDefaultProfileNames] count] > 0) {
397 398 399
        [_profilePopup addItemWithTitle:_NS("Organize profiles...")];
        [[_profilePopup lastItem] setTarget: self];
        [[_profilePopup lastItem] setAction: @selector(removeProfile:)];
400 401
    }

402 403 404
    [_profilePopup selectItemAtIndex: [self currentProfileIndex]];
    // Loading only non-default profiles ensures that vlcrc or command line settings are not overwritten
    if ([self currentProfileIndex] > 0)
405
        [self profileSelectorAction:self];
406 407
}

408 409 410
- (void)setWidgetValue: (id)widget forOption: (char *)psz_option enabled: (bool)b_state
{
    intf_thread_t *p_intf = getIntf();
411
    playlist_t *p_playlist = pl_Get(p_intf);
412 413

    vlc_value_t val;
414
    int i_type = config_GetType(psz_option) & VLC_VAR_CLASS;
415 416 417 418 419 420 421 422 423 424 425
    switch (i_type)
    {
    case VLC_VAR_BOOL:
    case VLC_VAR_INTEGER:
    case VLC_VAR_FLOAT:
    case VLC_VAR_STRING:
        break;
    default:
        msg_Err(p_intf, "%s variable is of an unsupported type (%d)", psz_option, i_type);
        return;
    }
426

427
    if (var_Create(p_playlist, psz_option, i_type | VLC_VAR_DOINHERIT) ||
428
        var_GetChecked(p_playlist, psz_option, i_type, &val)) {
429
        return;
430
    }
431 432 433 434 435

    if (i_type == VLC_VAR_BOOL || i_type == VLC_VAR_INTEGER)
    {
        if ([widget isKindOfClass: [NSSlider class]])
        {
436
            [widget setIntValue: (int)val.i_int];
437 438 439 440 441
            [widget setToolTip: [NSString stringWithFormat:@"%lli", val.i_int]];
        }
        else if ([widget isKindOfClass: [NSButton class]])
            [widget setState: val.i_int ? NSOnState : NSOffState];
        else if ([widget isKindOfClass: [NSTextField class]])
442
            [widget setIntValue: (int)val.i_int];
443
        else if ([widget isKindOfClass: [NSStepper class]])
444
            [widget setIntValue: (int)val.i_int];
445
        else if ([widget isKindOfClass: [NSPopUpButton class]])
446
            [widget selectItemWithTag: (int)val.i_int];
447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475
    }
    else if (i_type == VLC_VAR_FLOAT)
    {
        if ([widget isKindOfClass: [NSSlider class]])
        {
            [widget setFloatValue: val.f_float];
            [widget setToolTip: [NSString stringWithFormat:@"%0.3f", val.f_float]];
        }
    }
    else if (i_type == VLC_VAR_STRING)
    {
        if ([widget isKindOfClass: [NSPopUpButton class]])
        {
            for (NSMenuItem *item in [widget itemArray])
                if ([item representedObject] &&
                    !strcmp([[item representedObject] UTF8String], val.psz_string))
                {
                    [widget selectItemWithTitle: [item title]];
                    break;
                }
        }
        else if ([widget isKindOfClass: [NSTextField class]])
            [widget setStringValue: toNSStr(val.psz_string)];
        free(val.psz_string);
    }

    [widget setEnabled: b_state];
}

476
/// Sets widget values based on variables
477 478
- (void)resetValues
{
479
    intf_thread_t *p_intf = getIntf();
480
    playlist_t *p_playlist = pl_Get(p_intf);
481 482
    BOOL b_state;

483 484
    /* do we have any filter enabled? if yes, show it. */
    char * psz_vfilters;
485
    psz_vfilters = var_InheritString(p_playlist, "video-filter");
486
    if (psz_vfilters) {
487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505
        [_adjustCheckbox setState: (NSInteger)strstr(psz_vfilters, "adjust")];
        [_sharpenCheckbox setState: (NSInteger)strstr(psz_vfilters, "sharpen")];
        [_bandingCheckbox setState: (NSInteger)strstr(psz_vfilters, "gradfun")];
        [_grainCheckbox setState: (NSInteger)strstr(psz_vfilters, "grain")];
        [_transformCheckbox setState: (NSInteger)strstr(psz_vfilters, "transform")];
        [_zoomCheckbox setState: (NSInteger)strstr(psz_vfilters, "magnify")];
        [_puzzleCheckbox setState: (NSInteger)strstr(psz_vfilters, "puzzle")];
        [_thresholdCheckbox setState: (NSInteger)strstr(psz_vfilters, "colorthres")];
        [_sepiaCheckbox setState: (NSInteger)strstr(psz_vfilters, "sepia")];
        [_gradientCheckbox setState: (NSInteger)strstr(psz_vfilters, "gradient")];
        [_extractCheckbox setState: (NSInteger)strstr(psz_vfilters, "extract")];
        [_invertCheckbox setState: (NSInteger)strstr(psz_vfilters, "invert")];
        [_posterizeCheckbox setState: (NSInteger)strstr(psz_vfilters, "posterize")];
        [_blurCheckbox setState: (NSInteger)strstr(psz_vfilters, "motionblur")];
        [_motiondetectCheckbox setState: (NSInteger)strstr(psz_vfilters, "motiondetect")];
        [_watereffectCheckbox setState: (NSInteger)strstr(psz_vfilters, "ripple")];
        [_wavesCheckbox setState: (NSInteger)strstr(psz_vfilters, "wave")];
        [_psychedelicCheckbox setState: (NSInteger)strstr(psz_vfilters, "psychedelic")];
        [_anaglyphCheckbox setState: (NSInteger)strstr(psz_vfilters, "anaglyph")];
506
        free(psz_vfilters);
507
    } else {
508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526
        [_adjustCheckbox setState: NSOffState];
        [_sharpenCheckbox setState: NSOffState];
        [_bandingCheckbox setState: NSOffState];
        [_grainCheckbox setState: NSOffState];
        [_transformCheckbox setState: NSOffState];
        [_zoomCheckbox setState: NSOffState];
        [_puzzleCheckbox setState: NSOffState];
        [_thresholdCheckbox setState: NSOffState];
        [_sepiaCheckbox setState: NSOffState];
        [_gradientCheckbox setState: NSOffState];
        [_extractCheckbox setState: NSOffState];
        [_invertCheckbox setState: NSOffState];
        [_posterizeCheckbox setState: NSOffState];
        [_blurCheckbox setState: NSOffState];
        [_motiondetectCheckbox setState: NSOffState];
        [_watereffectCheckbox setState: NSOffState];
        [_wavesCheckbox setState: NSOffState];
        [_psychedelicCheckbox setState: NSOffState];
        [_anaglyphCheckbox setState: NSOffState];
527
    }
528

529
    psz_vfilters = var_InheritString(p_playlist, "sub-source");
530
    if (psz_vfilters) {
531 532
        [_addTextCheckbox setState: (NSInteger)strstr(psz_vfilters, "marq")];
        [_addLogoCheckbox setState: (NSInteger)strstr(psz_vfilters, "logo")];
533
        free(psz_vfilters);
534
    } else {
535 536
        [_addTextCheckbox setState: NSOffState];
        [_addLogoCheckbox setState: NSOffState];
537
    }
538

539
    psz_vfilters = var_InheritString(p_playlist, "video-splitter");
540
    if (psz_vfilters) {
541 542
        [_cloneCheckbox setState: (NSInteger)strstr(psz_vfilters, "clone")];
        [_wallCheckbox setState: (NSInteger)strstr(psz_vfilters, "wall")];
543 544
        free(psz_vfilters);
    } else {
545 546
        [_cloneCheckbox setState: NSOffState];
        [_wallCheckbox setState: NSOffState];
547 548
    }

549
    /* fetch and show the various values */
550
    b_state = [_adjustCheckbox state];
551 552 553 554 555 556
    [self setWidgetValue: _adjustHueSlider forOption: "hue" enabled: b_state];
    [self setWidgetValue: _adjustContrastSlider forOption: "contrast" enabled: b_state];
    [self setWidgetValue: _adjustBrightnessSlider forOption: "brightness" enabled: b_state];
    [self setWidgetValue: _adjustSaturationSlider forOption: "saturation" enabled: b_state];
    [self setWidgetValue: _adjustBrightnessCheckbox forOption: "brightness-threshold" enabled: b_state];
    [self setWidgetValue: _adjustGammaSlider forOption: "gamma" enabled: b_state];
557 558 559 560 561 562 563
    [_adjustBrightnessLabel setEnabled: b_state];
    [_adjustContrastLabel setEnabled: b_state];
    [_adjustGammaLabel setEnabled: b_state];
    [_adjustHueLabel setEnabled: b_state];
    [_adjustSaturationLabel setEnabled: b_state];
    [_adjustResetButton setEnabled: b_state];

564
    [self setWidgetValue: _sharpenSlider forOption: "sharpen-sigma" enabled: [_sharpenCheckbox state]];
565 566
    [_sharpenLabel setEnabled: [_sharpenCheckbox state]];

567
    [self setWidgetValue: _bandingSlider forOption: "gradfun-radius" enabled: [_bandingCheckbox state]];
568 569
    [_bandingLabel setEnabled: [_bandingCheckbox state]];

570
    [self setWidgetValue: _grainSlider forOption: "grain-variance" enabled: [_grainCheckbox state]];
571
    [_grainLabel setEnabled: [_grainCheckbox state]];
572

573 574 575 576
    [self setCropLeftValue: 0];
    [self setCropTopValue: 0];
    [self setCropRightValue: 0];
    [self setCropBottomValue: 0];
577 578
    [_cropSyncTopBottomCheckbox setState: NSOffState];
    [_cropSyncLeftRightCheckbox setState: NSOffState];
579

580 581
    [self setWidgetValue: _transformPopup forOption: "transform-type" enabled: [_transformCheckbox state]];

582
    b_state = [_puzzleCheckbox state];
583 584 585 586
    [self setWidgetValue: _puzzleColumnsTextField forOption: "puzzle-cols" enabled: b_state];
    [self setWidgetValue: _puzzleColumnsStepper forOption: "puzzle-cols" enabled: b_state];
    [self setWidgetValue: _puzzleRowsTextField forOption: "puzzle-rows" enabled: b_state];
    [self setWidgetValue: _puzzleRowsStepper forOption: "puzzle-rows" enabled: b_state];
587 588 589 590
    [_puzzleRowsLabel setEnabled: b_state];
    [_puzzleColumnsLabel setEnabled: b_state];

    b_state = [_cloneCheckbox state];
591 592
    [self setWidgetValue: _cloneNumberTextField forOption: "clone-count" enabled: b_state];
    [self setWidgetValue: _cloneNumberStepper forOption: "clone-count" enabled: b_state];
593
    [_cloneNumberLabel setEnabled: b_state];
594

595
    b_state = [_wallCheckbox state];
596 597 598 599
    [self setWidgetValue: _wallNumbersOfRowsTextField forOption: "wall-rows" enabled: b_state];
    [self setWidgetValue: _wallNumbersOfRowsStepper forOption: "wall-rows" enabled: b_state];
    [self setWidgetValue: _wallNumberOfColumnsTextField forOption: "wall-cols" enabled: b_state];
    [self setWidgetValue: _wallNumberOfColumnsStepper forOption: "wall-cols" enabled: b_state];
600 601 602 603
    [_wallNumbersOfRowsLabel setEnabled: b_state];
    [_wallNumberOfColumnsLabel setEnabled: b_state];

    b_state = [_thresholdCheckbox state];
604 605 606
    [self setWidgetValue: _thresholdColorTextField forOption: "colorthres-color" enabled: b_state];
    [self setWidgetValue: _thresholdSaturationSlider forOption: "colorthres-saturationthres" enabled: b_state];
    [self setWidgetValue: _thresholdSimilaritySlider forOption: "colorthres-similaritythres" enabled: b_state];
607 608 609
    [_thresholdColorLabel setEnabled: b_state];
    [_thresholdSaturationLabel setEnabled: b_state];
    [_thresholdSimilarityLabel setEnabled: b_state];
610

611
    b_state = [_sepiaCheckbox state];
612 613
    [self setWidgetValue: _sepiaTextField forOption: "sepia-intensity" enabled: b_state];
    [self setWidgetValue: _sepiaStepper forOption: "sepia-intensity" enabled: b_state];
614
    [_sepiaLabel setEnabled: b_state];
615

616
    b_state = [_gradientCheckbox state];
617 618 619
    [self setWidgetValue: _gradientModePopup forOption: "gradient-mode" enabled: b_state];
    [self setWidgetValue: _gradientCartoonCheckbox forOption: "gradient-cartoon" enabled: b_state];
    [self setWidgetValue: _gradientColorCheckbox forOption: "gradient-type" enabled: b_state];
620
    [_gradientModeLabel setEnabled: b_state];
621

622
    [self setWidgetValue: _extractTextField forOption: "extract-component" enabled: [_extractCheckbox state]];
623
    [_extractLabel setEnabled: [_extractCheckbox state]];
624

625
    b_state = [_posterizeCheckbox state];
626 627
    [self setWidgetValue: _posterizeTextField forOption: "posterize-level" enabled: b_state];
    [self setWidgetValue: _posterizeStepper forOption: "posterize-level" enabled: b_state];
628
    [_posterizeLabel setEnabled: b_state];
629

630
    [self setWidgetValue: _blurSlider forOption: "blur-factor" enabled: [_blurCheckbox state]];
631
    [_blurLabel setEnabled: [_blurCheckbox state]];
632

633
    b_state = [_addTextCheckbox state];
634 635
    [self setWidgetValue: _addTextTextTextField forOption: "marq-marquee" enabled: b_state];
    [self setWidgetValue: _addTextPositionPopup forOption: "marq-position" enabled: b_state];
636 637
    [_addTextPositionLabel setEnabled: b_state];
    [_addTextTextLabel setEnabled: b_state];
638

639
    b_state = [_addLogoCheckbox state];
640 641 642
    [self setWidgetValue: _addLogoLogoTextField forOption: "logo-file" enabled: b_state];
    [self setWidgetValue: _addLogoPositionPopup forOption: "logo-position" enabled: b_state];
    [self setWidgetValue: _addLogoTransparencySlider forOption: "logo-opacity" enabled: b_state];
643 644 645
    [_addLogoPositionLabel setEnabled: b_state];
    [_addLogoLogoLabel setEnabled: b_state];
    [_addLogoTransparencyLabel setEnabled: b_state];
646 647
}

648 649
- (NSString *)generateProfileString
{
650
    intf_thread_t *p_intf = getIntf();
651
    playlist_t *p_playlist = pl_Get(p_intf);
652
    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",
653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688
                     B64EncAndFree(var_InheritString(p_playlist, "video-filter")),
                     B64EncAndFree(var_InheritString(p_playlist, "sub-source")),
                     B64EncAndFree(var_InheritString(p_playlist, "video-splitter")),
                     0LL, // former "hue" value, deprecated since 3.0.0
                     var_InheritFloat(p_playlist, "contrast"),
                     var_InheritFloat(p_playlist, "brightness"),
                     var_InheritFloat(p_playlist, "saturation"),
                     var_InheritFloat(p_playlist, "gamma"),
                     var_InheritFloat(p_playlist, "sharpen-sigma"),
                     var_InheritInteger(p_playlist, "gradfun-radius"),
                     var_InheritFloat(p_playlist, "grain-variance"),
                     B64EncAndFree(var_InheritString(p_playlist, "transform-type")),
                     var_InheritInteger(p_playlist, "puzzle-rows"),
                     var_InheritInteger(p_playlist, "puzzle-cols"),
                     var_InheritInteger(p_playlist, "colorthres-color"),
                     var_InheritInteger(p_playlist, "colorthres-saturationthres"),
                     var_InheritInteger(p_playlist, "colorthres-similaritythres"),
                     var_InheritInteger(p_playlist, "sepia-intensity"),
                     B64EncAndFree(var_InheritString(p_playlist, "gradient-mode")),
                     (int64_t)var_InheritBool(p_playlist, "gradient-cartoon"),
                     var_InheritInteger(p_playlist, "gradient-type"),
                     var_InheritInteger(p_playlist, "extract-component"),
                     var_InheritInteger(p_playlist, "posterize-level"),
                     var_InheritInteger(p_playlist, "blur-factor"),
                     B64EncAndFree(var_InheritString(p_playlist, "marq-marquee")),
                     var_InheritInteger(p_playlist, "marq-position"),
                     B64EncAndFree(var_InheritString(p_playlist, "logo-file")),
                     var_InheritInteger(p_playlist, "logo-position"),
                     var_InheritInteger(p_playlist, "logo-opacity"),
                     var_InheritInteger(p_playlist, "clone-count"),
                     var_InheritInteger(p_playlist, "wall-rows"),
                     var_InheritInteger(p_playlist, "wall-cols"),
                     // version 2 of profile string:
                     (int64_t)var_InheritBool(p_playlist, "brightness-threshold"), // index: 32
                     // version 3 of profile string: (vlc-3.0.0)
                     var_InheritFloat(p_playlist, "hue") // index: 33
689 690 691 692 693
            ];
}

#pragma mark -
#pragma mark generic UI code
694 695 696

- (void)saveCurrentProfile
{
697 698 699 700
    NSInteger currentProfileIndex = [self currentProfileIndex];

    // Do not save default profile
    if (currentProfileIndex == 0) {
701
        return;
702
    }
703 704 705 706

    /* fetch all the current settings in a uniform string */
    NSString *newProfile = [self generateProfileString];

707
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
708
    NSMutableArray *workArray = [[NSMutableArray alloc] initWithArray:[defaults objectForKey:@"VideoEffectProfiles"]];
709
    if (currentProfileIndex >= [workArray count])
710 711
        return;

712
    [workArray replaceObjectAtIndex:currentProfileIndex withObject:newProfile];
713 714 715 716
    [defaults setObject:[NSArray arrayWithArray:workArray] forKey:@"VideoEffectProfiles"];
    [defaults synchronize];
}

717 718
- (void)saveCurrentProfileAtTerminate
{
719 720 721 722
    if ([self currentProfileIndex] > 0) {
        [self saveCurrentProfile];
        return;
    }
723

724 725 726 727 728
    // A new "Custom profile" is only created if the user wants to load this as new profile at startup ...
    if (_applyProfileCheckbox.state == NSOffState)
        return;

    // ... and some settings are changed
729
    NSString *newProfile = [self generateProfileString];
730
    if ([newProfile compare:[VLCVideoEffectsWindowController defaultProfileString]] == NSOrderedSame)
731 732
        return;

733
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749
    NSMutableArray *workArray = [[NSMutableArray alloc] initWithArray:[defaults objectForKey:@"VideoEffectProfiles"]];
    [workArray addObject:newProfile];
    [defaults setObject:[NSArray arrayWithArray:workArray] forKey:@"VideoEffectProfiles"];

    NSArray<NSString *> *profileNames = [defaults objectForKey:@"VideoEffectProfileNames"];
    NSString *newProfileName;

    unsigned int num_custom = 0;
    do
        newProfileName = [@"Custom" stringByAppendingString:[NSString stringWithFormat:@"%03i",num_custom++]];
    while ([profileNames containsObject:newProfileName]);

    workArray = [[NSMutableArray alloc] initWithArray:[defaults objectForKey:@"VideoEffectProfileNames"]];
    [workArray addObject:newProfileName];
    [defaults setObject:[NSArray arrayWithArray:workArray] forKey:@"VideoEffectProfileNames"];

750
    [self saveCurrentProfileIndex:([workArray count] - 1)];
751

752 753 754
    [defaults synchronize];
}

755 756
- (IBAction)toggleWindow:(id)sender
{
757 758
    if ([self.window isKeyWindow])
        [self.window orderOut:sender];
759
    else {
760
        [self.window setLevel: [[[VLCMain sharedInstance] voutProvider] currentStatusWindowLevel]];
761
        [self.window makeKeyAndOrderFront:sender];
762
    }
763 764 765 766
}

- (IBAction)profileSelectorAction:(id)sender
{
767
    [self saveCurrentProfile];
768 769 770

    [self saveCurrentProfileIndex:[_profilePopup indexOfSelectedItem]];

771
    [self loadProfile];
772
    [self resetValues];
773 774
}

775
- (void)addProfile:(id)sender
776 777
{
    /* show panel */
778
    [[_textfieldPanel window] setAppearance:[NSAppearance appearanceNamed:NSAppearanceNameVibrantDark]];
779 780 781 782
    [_textfieldPanel setTitleString:_NS("Duplicate current profile for a new profile")];
    [_textfieldPanel setSubTitleString:_NS("Enter a name for the new profile:")];
    [_textfieldPanel setCancelButtonString:_NS("Cancel")];
    [_textfieldPanel setOkButtonString:_NS("Save")];
783

784 785
    // TODO: Change to weak, when dropping 10.7 support
    __unsafe_unretained typeof(self) _self = self;
786
    [_textfieldPanel runModalForWindow:self.window completionHandler:^(NSInteger returnCode, NSString *resultingText) {
787

788
        NSInteger currentProfileIndex = [_self currentProfileIndex];
789
        if (returnCode != NSModalResponseOK) {
790
            [_profilePopup selectItemAtIndex:currentProfileIndex];
791 792
            return;
        }
793

794
        NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
795
        NSArray *profileNames = [defaults objectForKey:@"VideoEffectProfileNames"];
796

797 798
        // duplicate names are not allowed in the popup control
        if ([resultingText length] == 0 || [profileNames containsObject:resultingText]) {
799
            [_profilePopup selectItemAtIndex:currentProfileIndex];
800

801 802 803 804 805
            NSAlert *alert = [[NSAlert alloc] init];
            [alert setAlertStyle:NSCriticalAlertStyle];
            [alert setMessageText:_NS("Please enter a unique name for the new profile.")];
            [alert setInformativeText:_NS("Multiple profiles with the same name are not allowed.")];
            [alert beginSheetModalForWindow:_self.window
806
                          completionHandler:nil];
807 808
            return;
        }
809

810 811
        /* fetch all the current settings in a uniform string */
        NSString *newProfile = [_self generateProfileString];
812

813
        /* add string to user defaults as well as a label */
814

815 816 817
        NSMutableArray *workArray = [[NSMutableArray alloc] initWithArray:[defaults objectForKey:@"VideoEffectProfiles"]];
        [workArray addObject:newProfile];
        [defaults setObject:[NSArray arrayWithArray:workArray] forKey:@"VideoEffectProfiles"];
818 819

        [self saveCurrentProfileIndex:([workArray count] - 1)];
820

821 822 823
        workArray = [[NSMutableArray alloc] initWithArray:[defaults objectForKey:@"VideoEffectProfileNames"]];
        [workArray addObject:resultingText];
        [defaults setObject:[NSArray arrayWithArray:workArray] forKey:@"VideoEffectProfileNames"];
824

825 826
        /* save defaults */
        [defaults synchronize];
827

828 829 830
        /* refresh UI */
        [_self resetProfileSelector];
    }];
831 832
}

833
- (void)removeProfile:(id)sender
834 835
{
    /* show panel */
836
    [[_popupPanel window] setAppearance:[NSAppearance appearanceNamed:NSAppearanceNameVibrantDark]];
837 838 839 840
    [_popupPanel setTitleString:_NS("Remove a preset")];
    [_popupPanel setSubTitleString:_NS("Select the preset you would like to remove:")];
    [_popupPanel setOkButtonString:_NS("Remove")];
    [_popupPanel setCancelButtonString:_NS("Cancel")];
841
    [_popupPanel setPopupButtonContent:[self nonDefaultProfileNames]];
842

843 844
    // TODO: Change to weak, when dropping 10.7 support
    __unsafe_unretained typeof(self) _self = self;
845
    [_popupPanel runModalForWindow:self.window completionHandler:^(NSInteger returnCode, NSInteger selectedIndex) {
846

847 848

        NSInteger activeProfileIndex = [_self currentProfileIndex];
849

850
        if (returnCode != NSModalResponseOK) {
851
            [_profilePopup selectItemAtIndex:activeProfileIndex];
852 853
            return;
        }
854

855 856
        // Popup panel does not contain the "Default" entry
        selectedIndex++;
857

858
        NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
859 860 861 862
        /* remove selected profile from settings */
        NSMutableArray *workArray = [[NSMutableArray alloc] initWithArray: [defaults objectForKey:@"VideoEffectProfiles"]];
        [workArray removeObjectAtIndex:selectedIndex];
        [defaults setObject:[NSArray arrayWithArray:workArray] forKey:@"VideoEffectProfiles"];
863

864 865 866
        workArray = [[NSMutableArray alloc] initWithArray: [defaults objectForKey:@"VideoEffectProfileNames"]];
        [workArray removeObjectAtIndex:selectedIndex];
        [defaults setObject:[NSArray arrayWithArray:workArray] forKey:@"VideoEffectProfileNames"];
867

868 869
        if (activeProfileIndex >= selectedIndex)
            [self saveCurrentProfileIndex:(activeProfileIndex - 1)];
870

871 872
        /* save defaults */
        [defaults synchronize];
873

874 875 876
        /* refresh UI */
        [_self resetProfileSelector];
    }];
877 878
}

879 880 881 882 883
- (IBAction)applyProfileCheckboxChanged:(id)sender
{
    [[NSUserDefaults standardUserDefaults] setBool:[sender state] forKey:@"VideoEffectApplyProfileOnStartup"];
}

884 885 886 887
#pragma mark -
#pragma mark basic
- (IBAction)enableAdjust:(id)sender
{
888 889 890 891 892 893 894 895 896 897 898
    if (sender == _adjustResetButton) {
        [_adjustBrightnessSlider setFloatValue: 1.0];
        [_adjustContrastSlider setFloatValue: 1.0];
        [_adjustGammaSlider setFloatValue: 1.0];
        [_adjustHueSlider setFloatValue: 0];
        [_adjustSaturationSlider setFloatValue: 1.0];
        [_adjustBrightnessSlider setToolTip: [NSString stringWithFormat:@"%0.3f", 1.0]];
        [_adjustContrastSlider setToolTip: [NSString stringWithFormat:@"%0.3f", 1.0]];
        [_adjustGammaSlider setToolTip: [NSString stringWithFormat:@"%0.3f", 1.0]];
        [_adjustHueSlider setToolTip: [NSString stringWithFormat:@"%.0f", 0.0]];
        [_adjustSaturationSlider setToolTip: [NSString stringWithFormat:@"%0.3f", 1.0]];
899

900
        VLCCoreInteraction *vci_si = [VLCCoreInteraction sharedInstance];
901 902 903 904 905
        [vci_si setVideoFilterProperty: "brightness" forFilter: "adjust" withValue: (vlc_value_t){ .f_float = 1.f }];
        [vci_si setVideoFilterProperty: "contrast" forFilter: "adjust" withValue: (vlc_value_t){ .f_float = 1.f }];
        [vci_si setVideoFilterProperty: "gamma" forFilter: "adjust" withValue: (vlc_value_t){ .f_float = 1.f }];
        [vci_si setVideoFilterProperty: "hue" forFilter: "adjust" withValue: (vlc_value_t){ .f_float = .0f }];
        [vci_si setVideoFilterProperty: "saturation" forFilter: "adjust" withValue: (vlc_value_t){ .f_float = 1.f }];
906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922
    } else {
        BOOL b_state = [_adjustCheckbox state];

        [[VLCCoreInteraction sharedInstance] setVideoFilter: "adjust" on: b_state];
        [_adjustBrightnessSlider setEnabled: b_state];
        [_adjustBrightnessCheckbox setEnabled: b_state];
        [_adjustBrightnessLabel setEnabled: b_state];
        [_adjustContrastSlider setEnabled: b_state];
        [_adjustContrastLabel setEnabled: b_state];
        [_adjustGammaSlider setEnabled: b_state];
        [_adjustGammaLabel setEnabled: b_state];
        [_adjustHueSlider setEnabled: b_state];
        [_adjustHueLabel setEnabled: b_state];
        [_adjustSaturationSlider setEnabled: b_state];
        [_adjustSaturationLabel setEnabled: b_state];
        [_adjustResetButton setEnabled: b_state];
    }
923 924 925 926
}

- (IBAction)adjustSliderChanged:(id)sender
{
927
    char const *psz_property = nil;
928
    if (sender == _adjustBrightnessSlider)
929
        psz_property = "brightness";
930
    else if (sender == _adjustContrastSlider)
931
        psz_property = "contrast";
932
    else if (sender == _adjustGammaSlider)
933
        psz_property = "gamma";
934
    else if (sender == _adjustHueSlider)
935
        psz_property = "hue";
936
    else if (sender == _adjustSaturationSlider)
937
        psz_property = "saturation";
938 939
    assert(psz_property);

940
    [[VLCCoreInteraction sharedInstance] setVideoFilterProperty: psz_property forFilter: "adjust" withValue: getWidgetFloatValue(sender)];
941 942 943

    if (sender == _adjustHueSlider)
        [_adjustHueSlider setToolTip: [NSString stringWithFormat:@"%.0f", [_adjustHueSlider floatValue]]];
944 945
    else
        [sender setToolTip: [NSString stringWithFormat:@"%0.3f", [sender floatValue]]];
946 947 948 949
}

- (IBAction)enableAdjustBrightnessThreshold:(id)sender
{
950 951 952
    [[VLCCoreInteraction sharedInstance] setVideoFilterProperty: "brightness-threshold"
                                                      forFilter: "adjust"
                                                      withValue: getWidgetBoolValue(sender)];
953 954 955 956
}

- (IBAction)enableSharpen:(id)sender
{
957
    BOOL b_state = [_sharpenCheckbox state];
958

959
    [[VLCCoreInteraction sharedInstance] setVideoFilter: "sharpen" on: b_state];
960 961
    [_sharpenSlider setEnabled: b_state];
    [_sharpenLabel setEnabled: b_state];
962 963 964 965
}

- (IBAction)sharpenSliderChanged:(id)sender
{
966
    [[VLCCoreInteraction sharedInstance] setVideoFilterProperty: "sharpen-sigma" forFilter: "sharpen" withValue: getWidgetFloatValue(sender)];
967
    [sender setToolTip: [NSString stringWithFormat:@"%0.3f", [sender floatValue]]];
968 969 970 971
}

- (IBAction)enableBanding:(id)sender
{
972
    BOOL b_state = [_bandingCheckbox state];
973

974
    [[VLCCoreInteraction sharedInstance] setVideoFilter: "gradfun" on: b_state];
975 976
    [_bandingSlider setEnabled: b_state];
    [_bandingLabel setEnabled: b_state];
977 978 979 980
}

- (IBAction)bandingSliderChanged:(id)sender
{
981
    [[VLCCoreInteraction sharedInstance] setVideoFilterProperty: "gradfun-radius" forFilter: "gradfun" withValue: getWidgetIntValue(sender)];
982
    [sender setToolTip: [NSString stringWithFormat:@"%i", [sender intValue]]];
983 984 985 986
}

- (IBAction)enableGrain:(id)sender
{
987
    BOOL b_state = [_grainCheckbox state];
988

989
    [[VLCCoreInteraction sharedInstance] setVideoFilter: "grain" on: b_state];
990 991
    [_grainSlider setEnabled: b_state];
    [_grainLabel setEnabled: b_state];
992 993 994 995
}

- (IBAction)grainSliderChanged:(id)sender
{
996
    [[VLCCoreInteraction sharedInstance] setVideoFilterProperty: "grain-variance" forFilter: "grain" withValue: getWidgetFloatValue(sender)];
997
    [sender setToolTip: [NSString stringWithFormat:@"%0.3f", [sender floatValue]]];
998 999 1000 1001 1002
}


#pragma mark -
#pragma mark crop
1003

1004
- (IBAction)cropObjectChanged:(id)sender
1005
{
1006 1007
    if ([_cropSyncTopBottomCheckbox state]) {
        if (sender == _cropBottomTextField || sender == _cropBottomStepper)
1008 1009 1010
            [self setCropTopValue: [self cropBottomValue]];
        else
            [self setCropBottomValue: [self cropTopValue]];
1011
    }
1012 1013
    if ([_cropSyncLeftRightCheckbox state]) {
        if (sender == _cropRightTextField || sender == _cropRightStepper)
1014 1015 1016
            [self setCropLeftValue: [self cropRightValue]];
        else
            [self setCropRightValue: [self cropLeftValue]];
1017
    }
1018

1019 1020 1021 1022 1023 1024 1025 1026 1027 1028
    NSArray<NSValue *> *vouts = getVouts();
    if (vouts)
        for (NSValue *ptr in vouts) {
            vout_thread_t *p_vout = [ptr pointerValue];
            var_SetInteger(p_vout, "crop-top", [_cropTopTextField intValue]);
            var_SetInteger(p_vout, "crop-bottom", [_cropBottomTextField intValue]);
            var_SetInteger(p_vout, "crop-left", [_cropLeftTextField intValue]);
            var_SetInteger(p_vout, "crop-right", [_cropRightTextField intValue]);
            vlc_object_release(p_vout);
        }
1029 1030 1031 1032 1033 1034
}

#pragma mark -
#pragma mark geometry
- (IBAction)enableTransform:(id)sender
{
1035 1036
    [[VLCCoreInteraction sharedInstance] setVideoFilter: "transform" on: [_transformCheckbox state]];
    [_transformPopup setEnabled: [_transformCheckbox state]];
1037 1038 1039 1040
}

- (IBAction)transformModifierChanged:(id)sender
{
1041 1042
    vlc_value_t value = { .psz_string = (char *)[[[_transformPopup selectedItem] representedObject] UTF8String] };
    [[VLCCoreInteraction sharedInstance] setVideoFilterProperty: "transform-type" forFilter: "transform" withValue: value];
1043 1044 1045 1046
}

- (IBAction)enableZoom:(id)sender
{
1047
    [[VLCCoreInteraction sharedInstance] setVideoFilter: "magnify" on: [_zoomCheckbox state]];
1048 1049 1050 1051
}

- (IBAction)enablePuzzle:(id)sender
{
1052
    BOOL b_state = [_puzzleCheckbox state];
1053

1054
    [[VLCCoreInteraction sharedInstance] setVideoFilter: "puzzle" on: b_state];
1055 1056 1057 1058 1059 1060
    [_puzzleColumnsTextField setEnabled: b_state];
    [_puzzleColumnsStepper setEnabled: b_state];
    [_puzzleColumnsLabel setEnabled: b_state];
    [_puzzleRowsTextField setEnabled: b_state];
    [_puzzleRowsStepper setEnabled: b_state];
    [_puzzleRowsLabel setEnabled: b_state];
1061 1062 1063 1064
}

- (IBAction)puzzleModifierChanged:(id)sender
{
1065
    if (sender == _puzzleColumnsTextField || sender == _puzzleColumnsStepper)
1066
        [[VLCCoreInteraction sharedInstance] setVideoFilterProperty: "puzzle-cols" forFilter: "puzzle" withValue: getWidgetIntValue(sender)];
1067
    else
1068
        [[VLCCoreInteraction sharedInstance] setVideoFilterProperty: "puzzle-rows" forFilter: "puzzle" withValue: getWidgetIntValue(sender)];
1069 1070
}

1071 1072
- (IBAction)enableClone:(id)sender
{
1073
    BOOL b_state = [_cloneCheckbox state];
1074

1075 1076 1077
    if (b_state && [_wallCheckbox state]) {
        [_wallCheckbox setState: NSOffState];
        [self enableWall:_wallCheckbox];
1078 1079
    }

1080
    [[VLCCoreInteraction sharedInstance] setVideoFilter: "clone" on: b_state];
1081 1082 1083
    [_cloneNumberLabel setEnabled: b_state];
    [_cloneNumberTextField setEnabled: b_state];
    [_cloneNumberStepper setEnabled: b_state];
1084 1085 1086 1087
}

- (IBAction)cloneModifierChanged:(id)sender
{
1088
    [[VLCCoreInteraction sharedInstance] setVideoFilterProperty: "clone-count" forFilter: "clone" withValue: getWidgetIntValue(_cloneNumberTextField)];
1089 1090 1091 1092
}

- (IBAction)enableWall:(id)sender
{
1093
    BOOL b_state = [_wallCheckbox state];
1094

1095 1096 1097
    if (b_state && [_cloneCheckbox state]) {
        [_cloneCheckbox setState: NSOffState];
        [self enableClone:_cloneCheckbox];
1098 1099
    }

1100
    [[VLCCoreInteraction sharedInstance] setVideoFilter: "wall" on: b_state];
1101 1102 1103
    [_wallNumberOfColumnsTextField setEnabled: b_state];
    [_wallNumberOfColumnsStepper setEnabled: b_state];
    [_wallNumberOfColumnsLabel setEnabled: b_state];
1104

1105 1106 1107
    [_wallNumbersOfRowsTextField setEnabled: b_state];
    [_wallNumbersOfRowsStepper setEnabled: b_state];
    [_wallNumbersOfRowsLabel setEnabled: b_state];
1108 1109 1110 1111
}

- (IBAction)wallModifierChanged:(id)sender
{
1112
    char const *psz_property;
1113
    if (sender == _wallNumberOfColumnsTextField || sender == _wallNumberOfColumnsStepper)
1114
        psz_property = "wall-cols";
1115
    else
1116 1117
        psz_property = "wall-rows";
    [[VLCCoreInteraction sharedInstance] setVideoFilterProperty: psz_property forFilter: "wall" withValue: getWidgetIntValue(sender)];
1118
}
1119 1120 1121 1122 1123

#pragma mark -
#pragma mark color
- (IBAction)enableThreshold:(id)sender
{
1124
    BOOL b_state = [_thresholdCheckbox state];
1125

1126
    [[VLCCoreInteraction sharedInstance] setVideoFilter: "colorthres" on: b_state];
1127 1128 1129 1130 1131 1132
    [_thresholdColorTextField setEnabled: b_state];
    [_thresholdColorLabel setEnabled: b_state];
    [_thresholdSaturationSlider setEnabled: b_state];
    [_thresholdSaturationLabel setEnabled: b_state];
    [_thresholdSimilaritySlider setEnabled: b_state];
    [_thresholdSimilarityLabel setEnabled: b_state];
1133 1134 1135 1136
}

- (IBAction)thresholdModifierChanged:(id)sender
{
1137
    if (sender == _thresholdColorTextField)
1138
        [[VLCCoreInteraction sharedInstance] setVideoFilterProperty: "colorthres-color" forFilter: "colorthres" withValue: getWidgetIntValue(sender)];
1139
    else if (sender == _thresholdSaturationSlider) {
1140
        [[VLCCoreInteraction sharedInstance] setVideoFilterProperty: "colorthres-saturationthres" forFilter: "colorthres" withValue: getWidgetIntValue(sender)];
1141
        [_thresholdSaturationSlider setToolTip: [NSString stringWithFormat:@"%i", [_thresholdSaturationSlider intValue]]];
1142
    } else {
1143
        [[VLCCoreInteraction sharedInstance] setVideoFilterProperty: "colorthres-similaritythres" forFilter: "colorthres" withValue: getWidgetIntValue(sender)];
1144
        [_thresholdSimilaritySlider setToolTip: [NSString stringWithFormat:@"%i", [_thresholdSimilaritySlider intValue]]];
1145
    }
1146 1147 1148 1149
}

- (IBAction)enableSepia:(id)sender
{
1150
    BOOL b_state = [_sepiaCheckbox state];
1151

1152
    [[VLCCoreInteraction sharedInstance] setVideoFilter: "sepia" on: b_state];
1153 1154 1155
    [_sepiaTextField setEnabled: b_state];
    [_sepiaStepper setEnabled: b_state];
    [_sepiaLabel setEnabled: b_state];
1156 1157 1158 1159
}

- (IBAction)sepiaModifierChanged:(id)sender
{
1160
    [[VLCCoreInteraction sharedInstance] setVideoFilterProperty: "sepia-intensity" forFilter: "sepia" withValue: getWidgetIntValue(sender)];
1161 1162 1163 1164
}

- (IBAction)enableGradient:(id)sender
{
1165
    BOOL b_state = [_gradientCheckbox state];
1166

1167
    [[VLCCoreInteraction sharedInstance] setVideoFilter: "gradient" on: b_state];
1168 1169 1170 1171
    [_gradientModePopup setEnabled: b_state];
    [_gradientModeLabel setEnabled: b_state];
    [_gradientColorCheckbox setEnabled: b_state];
    [_gradientCartoonCheckbox setEnabled: b_state];
1172 1173 1174 1175
}

- (IBAction)gradientModifierChanged:(id)sender
{
1176
    if (sender == _gradientModePopup) {
1177 1178
        vlc_value_t value = { .psz_string = (char *)[[[sender selectedItem] representedObject] UTF8String] };
        [[VLCCoreInteraction sharedInstance] setVideoFilterProperty: "gradient-mode" forFilter: "gradient" withValue: value];
1179
    } else if (sender == _gradientColorCheckbox)
1180
        [[VLCCoreInteraction sharedInstance] setVideoFilterProperty: "gradient-type" forFilter: "gradient" withValue: getWidgetBoolValue(sender)];
1181
    else
1182
        [[VLCCoreInteraction sharedInstance] setVideoFilterProperty: "gradient-cartoon" forFilter: "gradient" withValue: getWidgetBoolValue(sender)];
1183 1184 1185 1186
}

- (IBAction)enableExtract:(id)sender
{
1187
    BOOL b_state = [_extractCheckbox state];
1188
    [[VLCCoreInteraction sharedInstance] setVideoFilter: "extract" on: b_state];
1189 1190
    [_extractTextField setEnabled: b_state];
    [_extractLabel setEnabled: b_state];
1191 1192 1193 1194
}

- (IBAction)extractModifierChanged:(id)sender
{
1195
    [[VLCCoreInteraction sharedInstance] setVideoFilterProperty: "extract-component" forFilter: "extract" withValue: getWidgetIntValue(sender)];
1196 1197 1198 1199
}

- (IBAction)enableInvert:(id)sender
{
1200
    [[VLCCoreInteraction sharedInstance] setVideoFilter: "invert" on: [_invertCheckbox state]];
1201 1202 1203 1204
}

- (IBAction)enablePosterize:(id)sender
{
1205
    BOOL b_state = [_posterizeCheckbox state];
1206

1207
    [[VLCCoreInteraction sharedInstance] setVideoFilter: "posterize" on: b_state];
1208 1209 1210
    [_posterizeTextField setEnabled: b_state];
    [_posterizeStepper setEnabled: b_state];
    [_posterizeLabel setEnabled: b_state];
1211 1212 1213 1214
}

- (IBAction)posterizeModifierChanged:(id)sender
{
1215
    [[VLCCoreInteraction sharedInstance] setVideoFilterProperty: "posterize-level" forFilter: "posterize" withValue: getWidgetIntValue(sender)];