git.y1.nz

SameBoy

Accurate GB/GBC emulator
download: https://git.y1.nz/archives/sameboy.tar.gz
README | Files | Log | Refs | LICENSE

Cocoa/GBPreferencesWindow.m

      1 #import "GBPreferencesWindow.h"
      2 #import "GBJoyConManager.h"
      3 #import "NSString+StringForKey.h"
      4 #import "GBButtons.h"
      5 #import "BigSurToolbar.h"
      6 #import "GBViewMetal.h"
      7 #import "GBWarningPopover.h"
      8 #import <Carbon/Carbon.h>
      9 
     10 @implementation GBPreferencesWindow
     11 {
     12     bool is_button_being_modified;
     13     NSInteger button_being_modified;
     14     signed joystick_configuration_state;
     15     NSString *joystick_being_configured;
     16     bool joypad_wait;
     17 
     18     NSEventModifierFlags previousModifiers;
     19 }
     20 
     21 - (NSWindowToolbarStyle)toolbarStyle
     22 {
     23     return NSWindowToolbarStylePreference;
     24 }
     25 
     26 - (void)close
     27 {
     28     joystick_configuration_state = -1;
     29     [self.configureJoypadButton setEnabled:true];
     30     [self.skipButton setEnabled:false];
     31     [self.configureJoypadButton setTitle:@"Configure Controller"];
     32     [super close];
     33 }
     34 
     35 static inline NSString *keyEquivalentString(NSMenuItem *item)
     36 {
     37     return [NSString stringWithFormat:@"%s%@", (item.keyEquivalentModifierMask & NSEventModifierFlagShift)? "^":"", item.keyEquivalent];
     38 }
     39 
     40 - (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
     41 {
     42     if (self.playerListButton.selectedTag == 0) {
     43         return GBKeyboardButtonCount;
     44     }
     45     return GBPerPlayerButtonCount;
     46 }
     47 
     48 - (unsigned) usesForKey:(unsigned) key
     49 {
     50     unsigned ret = 0;
     51     for (unsigned player = 4; player--;) {
     52         for (unsigned button = player == 0? GBKeyboardButtonCount:GBPerPlayerButtonCount; button--;) {
     53             NSNumber *other = [[NSUserDefaults standardUserDefaults] valueForKey:button_to_preference_name(button, player)];
     54             if (other && [other unsignedIntValue] == key) {
     55                 ret++;
     56             }
     57         }
     58     }
     59     return ret;
     60 }
     61 
     62 - (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
     63 {
     64     if ([tableColumn.identifier isEqualToString:@"keyName"]) {
     65         return GBButtonNames[row];
     66     }
     67 
     68     if (is_button_being_modified && button_being_modified == row) {
     69         return @"Select a new key…";
     70     }
     71     
     72     NSNumber *key = [[NSUserDefaults standardUserDefaults] valueForKey:button_to_preference_name(row, self.playerListButton.selectedTag)];
     73     if (key) {
     74         if ([self usesForKey:[key unsignedIntValue]] > 1) {
     75             return [[NSAttributedString alloc] initWithString:[NSString displayStringForKeyCode: [key unsignedIntegerValue]]
     76                                                    attributes:@{NSForegroundColorAttributeName: [NSColor colorWithRed:0.9375 green:0.25 blue:0.25 alpha:1.0],
     77                                                                 NSFontAttributeName: [NSFont boldSystemFontOfSize:[NSFont systemFontSize]]
     78                                                    }];
     79         }
     80         return [NSString displayStringForKeyCode: [key unsignedIntegerValue]];
     81     }
     82 
     83     return @"";
     84 }
     85 
     86 - (BOOL)tableView:(NSTableView *)tableView shouldEditTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
     87 {
     88 
     89     dispatch_async(dispatch_get_main_queue(), ^{
     90         is_button_being_modified = true;
     91         button_being_modified = row;
     92         tableView.enabled = false;
     93         self.playerListButton.enabled = false;
     94         [tableView reloadData];
     95         [self makeFirstResponder:self];
     96     });
     97     return false;
     98 }
     99 
    100 -(void)keyDown:(NSEvent *)theEvent
    101 {
    102     if (!is_button_being_modified) {
    103         if (self.firstResponder != self.controlsTableView && [theEvent type] != NSEventTypeFlagsChanged) {
    104             [super keyDown:theEvent];
    105         }
    106         return;
    107     }
    108 
    109     is_button_being_modified = false;
    110 
    111     [[NSUserDefaults standardUserDefaults] setInteger:theEvent.keyCode
    112                                               forKey:button_to_preference_name(button_being_modified, self.playerListButton.selectedTag)];
    113     self.controlsTableView.enabled = true;
    114     self.playerListButton.enabled = true;
    115     [self.controlsTableView reloadData];
    116     [self makeFirstResponder:self.controlsTableView];
    117 }
    118 
    119 - (void) flagsChanged:(NSEvent *)event
    120 {
    121     if (event.modifierFlags > previousModifiers) {
    122         [self keyDown:event];
    123     }
    124     
    125     previousModifiers = event.modifierFlags;
    126 }
    127 
    128 - (void)updatePalettesMenu
    129 {
    130     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    131     NSDictionary *themes = [defaults dictionaryForKey:@"GBThemes"];
    132     NSMenu *menu = _colorPalettePopupButton.menu;
    133     while (menu.itemArray.count != 4) {
    134         [menu removeItemAtIndex:4];
    135     }
    136     [menu addItem:[NSMenuItem separatorItem]];
    137     for (NSString *name in [themes.allKeys sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)]) {
    138         NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:name action:nil keyEquivalent:@""];
    139         item.tag = -2;
    140         [menu addItem:item];
    141     }
    142     if (themes) {
    143         [menu addItem:[NSMenuItem separatorItem]];
    144     }
    145     NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:@"Custom…" action:nil keyEquivalent:@""];
    146     item.tag = -1;
    147     [menu addItem:item];
    148 }
    149 
    150 - (IBAction)colorPaletteChanged:(id)sender
    151 {
    152     signed tag = [sender selectedItem].tag;
    153     if (tag == -2) {
    154         [[NSUserDefaults standardUserDefaults] setObject:@(-1)
    155                                                   forKey:@"GBColorPalette"];
    156         [[NSUserDefaults standardUserDefaults] setObject:[sender selectedItem].title
    157                                                   forKey:@"GBCurrentTheme"];
    158 
    159     }
    160     else if (tag == -1) {
    161         [[NSUserDefaults standardUserDefaults] setObject:@(-1)
    162                                                   forKey:@"GBColorPalette"];
    163         [_paletteEditorController awakeFromNib];
    164         [self beginSheet:_paletteEditor completionHandler:^(NSModalResponse returnCode) {
    165             [self updatePalettesMenu];
    166             [_colorPalettePopupButton selectItemWithTitle:[[NSUserDefaults standardUserDefaults] stringForKey:@"GBCurrentTheme"] ?: @""];
    167         }];
    168     }
    169     else {
    170         [[NSUserDefaults standardUserDefaults] setObject:@([sender selectedItem].tag)
    171                                                   forKey:@"GBColorPalette"];
    172     }
    173     [[NSNotificationCenter defaultCenter] postNotificationName:@"GBColorPaletteChanged" object:nil];
    174 }
    175 
    176 - (IBAction)hotkey1Changed:(id)sender
    177 {
    178     [[NSUserDefaults standardUserDefaults] setObject:keyEquivalentString([sender selectedItem])
    179                                               forKey:@"GBJoypadHotkey1"];
    180 }
    181 
    182 - (IBAction)hotkey2Changed:(id)sender
    183 {
    184     [[NSUserDefaults standardUserDefaults] setObject:keyEquivalentString([sender selectedItem])
    185                                               forKey:@"GBJoypadHotkey2"];
    186 }
    187 
    188 
    189 - (IBAction) configureJoypad:(id)sender
    190 {
    191     [self.configureJoypadButton setEnabled:false];
    192     [self.skipButton setEnabled:true];
    193     joystick_being_configured = nil;
    194     [self advanceConfigurationStateMachine];
    195 }
    196 
    197 - (IBAction) skipButton:(id)sender
    198 {
    199     [self advanceConfigurationStateMachine];
    200 }
    201 
    202 - (void) advanceConfigurationStateMachine
    203 {
    204     joystick_configuration_state++;
    205     if (joystick_configuration_state == GBUnderclock) {
    206         [self.configureJoypadButton setTitle:@"Press Button for Slo-Mo"]; // Full name is too long :<
    207     }
    208     else if (joystick_configuration_state < GBTotalButtonCount) {
    209         [self.configureJoypadButton setTitle:[NSString stringWithFormat:@"Press Button for %@", GBButtonNames[joystick_configuration_state]]];
    210     }
    211     else {
    212         joystick_configuration_state = -1;
    213         [self.configureJoypadButton setEnabled:true];
    214         [self.skipButton setEnabled:false];
    215         [self.configureJoypadButton setTitle:@"Configure Joypad"];
    216     }
    217 }
    218 
    219 - (void)controller:(JOYController *)controller buttonChangedState:(JOYButton *)button
    220 {
    221     /* Debounce */
    222     if (joypad_wait) return;
    223     joypad_wait = true;
    224     dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    225         joypad_wait = false;
    226     });
    227         
    228     if (!button.isPressed) return;
    229     if (joystick_configuration_state == -1) return;
    230     if (joystick_configuration_state == GBTotalButtonCount) return;
    231     if (!joystick_being_configured) {
    232         joystick_being_configured = controller.uniqueID;
    233     }
    234     else if (![joystick_being_configured isEqualToString:controller.uniqueID]) {
    235         return;
    236     }
    237     
    238     NSMutableDictionary *instance_mappings = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitInstanceMapping"] mutableCopy];
    239     
    240     NSMutableDictionary *name_mappings = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitNameMapping"] mutableCopy];
    241 
    242     
    243     if (!instance_mappings) {
    244         instance_mappings = [[NSMutableDictionary alloc] init];
    245     }
    246     
    247     if (!name_mappings) {
    248         name_mappings = [[NSMutableDictionary alloc] init];
    249     }
    250     
    251     NSMutableDictionary *mapping = nil;
    252     if (joystick_configuration_state != 0) {
    253         mapping = [instance_mappings[controller.uniqueID] mutableCopy];
    254     }
    255     else {
    256         mapping = [[NSMutableDictionary alloc] init];
    257     }
    258 
    259     
    260     static const unsigned gb_to_joykit[] = {
    261     [GBRight] = JOYButtonUsageDPadRight,
    262     [GBLeft] = JOYButtonUsageDPadLeft,
    263     [GBUp] = JOYButtonUsageDPadUp,
    264     [GBDown] = JOYButtonUsageDPadDown,
    265     [GBA] = JOYButtonUsageA,
    266     [GBB] = JOYButtonUsageB,
    267     [GBSelect] = JOYButtonUsageSelect,
    268     [GBStart] = JOYButtonUsageStart,
    269     [GBRapidA] = GBJoyKitRapidA,
    270     [GBRapidB] = GBJoyKitRapidB,
    271     [GBTurbo] = JOYButtonUsageL1,
    272     [GBRewind] = JOYButtonUsageL2,
    273     [GBUnderclock] = JOYButtonUsageR1,
    274     [GBHotkey1] = GBJoyKitHotkey1,
    275     [GBHotkey2] = GBJoyKitHotkey2,
    276     };
    277     
    278     if (joystick_configuration_state == GBUnderclock) {
    279         mapping[@"AnalogUnderclock"] = nil;
    280         double max = 0;
    281         for (JOYAxis *axis in controller.axes) {
    282             if ((axis.value > 0.5 || (axis.equivalentButtonUsage == button.usage)) && axis.value >= max) {
    283                 mapping[@"AnalogUnderclock"] = @(axis.uniqueID);
    284                 break;
    285             }
    286         }
    287     }
    288     
    289     if (joystick_configuration_state == GBTurbo) {
    290         mapping[@"AnalogTurbo"] = nil;
    291         double max = 0;
    292         for (JOYAxis *axis in controller.axes) {
    293             if ((axis.value > 0.5 || (axis.equivalentButtonUsage == button.usage)) && axis.value >= max) {
    294                 max = axis.value;
    295                 mapping[@"AnalogTurbo"] = @(axis.uniqueID);
    296             }
    297         }
    298     }
    299     
    300     mapping[n2s(button.uniqueID)] = @(gb_to_joykit[joystick_configuration_state]);
    301     
    302     instance_mappings[controller.uniqueID] = mapping;
    303     name_mappings[controller.deviceName] = mapping;
    304     [[NSUserDefaults standardUserDefaults] setObject:instance_mappings forKey:@"JoyKitInstanceMapping"];
    305     [[NSUserDefaults standardUserDefaults] setObject:name_mappings forKey:@"JoyKitNameMapping"];
    306     [self advanceConfigurationStateMachine];
    307 }
    308 
    309 - (void)awakeFromNib
    310 {
    311     [super awakeFromNib];
    312     [self updateBootROMFolderButton];
    313     [[NSDistributedNotificationCenter defaultCenter] addObserver:self.controlsTableView selector:@selector(reloadData) name:(NSString*)kTISNotifySelectedKeyboardInputSourceChanged object:nil];
    314     [JOYController registerListener:self];
    315     joystick_configuration_state = -1;
    316     [self refreshJoypadMenu:nil];
    317     
    318     NSString *keyEquivalent = [[NSUserDefaults standardUserDefaults] stringForKey:@"GBJoypadHotkey1"];
    319     for (NSMenuItem *item in _hotkey1PopupButton.menu.itemArray) {
    320         if ([keyEquivalent isEqualToString:keyEquivalentString(item)]) {
    321             [_hotkey1PopupButton selectItem:item];
    322             break;
    323         }
    324     }
    325     
    326     keyEquivalent = [[NSUserDefaults standardUserDefaults] stringForKey:@"GBJoypadHotkey2"];
    327     for (NSMenuItem *item in _hotkey2PopupButton.menu.itemArray) {
    328         if ([keyEquivalent isEqualToString:keyEquivalentString(item)]) {
    329             [_hotkey2PopupButton selectItem:item];
    330             break;
    331         }
    332     }
    333     
    334     [self updatePalettesMenu];
    335     NSInteger mode = [[NSUserDefaults standardUserDefaults] integerForKey:@"GBColorPalette"];
    336     if (mode >= 0) {
    337         [_colorPalettePopupButton selectItemWithTag:mode];
    338     }
    339     else {
    340         [_colorPalettePopupButton selectItemWithTitle:[[NSUserDefaults standardUserDefaults] stringForKey:@"GBCurrentTheme"] ?: @""];
    341     }
    342     
    343     _fontSizeStepper.intValue = [[NSUserDefaults standardUserDefaults] integerForKey:@"GBDebuggerFontSize"];
    344     [self updateFonts];
    345     
    346     double cap = [[NSUserDefaults standardUserDefaults] doubleForKey:@"GBTurboCap"];
    347     if (cap) {
    348         _turboCapSlider.intValue = round(cap * 100);
    349         _turboCapButton.state = NSOnState;
    350     }
    351     [self turboCapToggled:_turboCapButton];
    352 }
    353 
    354 - (IBAction)fontSizeChanged:(id)sender
    355 {
    356     NSString *selectedFont = [[NSUserDefaults standardUserDefaults] stringForKey:@"GBDebuggerFont"];
    357     [[NSUserDefaults standardUserDefaults] setInteger:[sender intValue] forKey:@"GBDebuggerFontSize"];
    358     [_fontPopupButton setDisplayTitle:[NSString stringWithFormat:@"%@ %upt", selectedFont, (unsigned)[[NSUserDefaults standardUserDefaults] integerForKey:@"GBDebuggerFontSize"]]];
    359 }
    360 
    361 - (IBAction)fontChanged:(id)sender
    362 {
    363     NSString *selectedFont = _fontPopupButton.selectedItem.title;
    364     [[NSUserDefaults standardUserDefaults] setObject:selectedFont forKey:@"GBDebuggerFont"];
    365     [_fontPopupButton setDisplayTitle:[NSString stringWithFormat:@"%@ %upt", selectedFont, (unsigned)[[NSUserDefaults standardUserDefaults] integerForKey:@"GBDebuggerFontSize"]]];
    366 
    367 }
    368 
    369 - (void)updateFonts
    370 {
    371     dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    372         NSFontManager *fontManager = [NSFontManager sharedFontManager];
    373         NSArray *allFamilies = [fontManager availableFontFamilies];
    374         NSMutableSet *families = [NSMutableSet set];
    375         for (NSString *family in allFamilies) {
    376             if ([fontManager fontNamed:family hasTraits:NSFixedPitchFontMask]) {
    377                 [families addObject:family];
    378             }
    379         }
    380         
    381         bool hasSFMono = false;
    382         if (@available(macOS 10.15, *)) {
    383             hasSFMono = [[NSFont monospacedSystemFontOfSize:12 weight:NSFontWeightRegular].displayName containsString:@"SF"];
    384         }
    385         
    386         if (hasSFMono) {
    387             [families addObject:@"SF Mono"];
    388         }
    389     
    390         NSArray *sortedFamilies = [[families allObjects] sortedArrayUsingSelector:@selector(compare:)];
    391 
    392         dispatch_async(dispatch_get_main_queue(), ^{
    393             if (![families containsObject:[[NSUserDefaults standardUserDefaults] stringForKey:@"GBDebuggerFont"]]) {
    394                 [[NSUserDefaults standardUserDefaults] removeObjectForKey:@"GBDebuggerFont"];
    395             }
    396             
    397             [_fontPopupButton.menu removeAllItems];
    398             for (NSString *family in sortedFamilies) {
    399                 [_fontPopupButton addItemWithTitle:family];
    400             }
    401             NSString *selectedFont = [[NSUserDefaults standardUserDefaults] stringForKey:@"GBDebuggerFont"];
    402             [_fontPopupButton selectItemWithTitle:selectedFont];
    403             [_fontPopupButton setDisplayTitle:[NSString stringWithFormat:@"%@ %upt", selectedFont, (unsigned)[[NSUserDefaults standardUserDefaults] integerForKey:@"GBDebuggerFontSize"]]];
    404         });
    405     });
    406 }
    407 
    408 - (void)dealloc
    409 {
    410     [JOYController unregisterListener:self];
    411     [[NSDistributedNotificationCenter defaultCenter] removeObserver:self.controlsTableView];
    412 }
    413 
    414 - (IBAction)selectOtherBootROMFolder:(id)sender
    415 {
    416     NSOpenPanel *panel = [[NSOpenPanel alloc] init];
    417     [panel setCanChooseDirectories:true];
    418     [panel setCanChooseFiles:false];
    419     [panel setPrompt:@"Select"];
    420     [panel setDirectoryURL:[[NSUserDefaults standardUserDefaults] URLForKey:@"GBBootROMsFolder"]];
    421     [panel beginSheetModalForWindow:self completionHandler:^(NSModalResponse result) {
    422         if (result == NSModalResponseOK) {
    423             NSURL *url = [[panel URLs] firstObject];
    424             [[NSUserDefaults standardUserDefaults] setURL:url forKey:@"GBBootROMsFolder"];
    425         }
    426         [self updateBootROMFolderButton];
    427     }];
    428 
    429 }
    430 
    431 - (void) updateBootROMFolderButton
    432 {
    433     NSURL *url = [[NSUserDefaults standardUserDefaults] URLForKey:@"GBBootROMsFolder"];
    434     BOOL is_dir = false;
    435     [[NSFileManager defaultManager] fileExistsAtPath:[url path] isDirectory:&is_dir];
    436     if (!is_dir) url = nil;
    437     
    438     if (url) {
    439         [self.bootROMsFolderItem setTitle:[url lastPathComponent]];
    440         NSImage *icon = [[NSWorkspace sharedWorkspace] iconForFile:[url path]];
    441         [icon setSize:NSMakeSize(16, 16)];
    442         [self.bootROMsFolderItem setHidden:false];
    443         [self.bootROMsFolderItem setImage:icon];
    444         [self.bootROMsButton selectItemAtIndex:1];
    445     }
    446     else {
    447         [self.bootROMsFolderItem setHidden:true];
    448         [self.bootROMsButton selectItemAtIndex:0];
    449     }
    450 }
    451 
    452 - (IBAction)useBuiltinBootROMs:(id)sender
    453 {
    454     [[NSUserDefaults standardUserDefaults] setURL:nil forKey:@"GBBootROMsFolder"];
    455     [self updateBootROMFolderButton];
    456 }
    457 
    458 - (void)controllerConnected:(JOYController *)controller
    459 {
    460     dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    461         [self refreshJoypadMenu:nil];
    462     });
    463 }
    464 
    465 - (void)controllerDisconnected:(JOYController *)controller
    466 {
    467     dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    468         [self refreshJoypadMenu:nil];
    469     });
    470 }
    471 
    472 - (IBAction)refreshJoypadMenu:(id)sender
    473 {
    474     bool preferred_is_connected = false;
    475     NSString *player_string = n2s(self.playerListButton.selectedTag);
    476     NSString *selected_controller = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitDefaultControllers"][player_string];
    477     
    478     [self.preferredJoypadButton removeAllItems];
    479     [self.preferredJoypadButton addItemWithTitle:@"None"];
    480     for (JOYController *controller in [JOYController allControllers]) {
    481         [self.preferredJoypadButton addItemWithTitle:[NSString stringWithFormat:@"%@ (%@)", controller.deviceName, controller.uniqueID]];
    482         
    483         self.preferredJoypadButton.lastItem.identifier = controller.uniqueID;
    484         
    485         if ([controller.uniqueID isEqualToString:selected_controller]) {
    486             preferred_is_connected = true;
    487             [self.preferredJoypadButton selectItem:self.preferredJoypadButton.lastItem];
    488         }
    489     }
    490     
    491     if (!preferred_is_connected && selected_controller) {
    492         [self.preferredJoypadButton addItemWithTitle:[NSString stringWithFormat:@"Unavailable Controller (%@)", selected_controller]];
    493         self.preferredJoypadButton.lastItem.identifier = selected_controller;
    494         [self.preferredJoypadButton selectItem:self.preferredJoypadButton.lastItem];
    495     }
    496     
    497 
    498     if (!selected_controller) {
    499         [self.preferredJoypadButton selectItemWithTitle:@"None"];
    500     }
    501     [self.controlsTableView reloadData];
    502 }
    503 
    504 - (IBAction)changeDefaultJoypad:(id)sender
    505 {
    506     NSMutableDictionary *default_joypads = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitDefaultControllers"] mutableCopy];
    507     if (!default_joypads) {
    508         default_joypads = [[NSMutableDictionary alloc] init];
    509     }
    510 
    511     NSString *player_string = n2s(self.playerListButton.selectedTag);
    512     if ([[sender titleOfSelectedItem] isEqualToString:@"None"]) {
    513         [default_joypads removeObjectForKey:player_string];
    514     }
    515     else {
    516         default_joypads[player_string] = [[sender selectedItem] identifier];
    517     }
    518     [[NSUserDefaults standardUserDefaults] setObject:default_joypads forKey:@"JoyKitDefaultControllers"];
    519 }
    520 
    521 - (IBAction)displayColorCorrectionHelp:(id)sender
    522 {
    523     [GBWarningPopover popoverWithContents:
    524          GB_inline_const(NSString *[], {
    525             [GB_COLOR_CORRECTION_DISABLED] = @"Colors are directly interpreted as sRGB, resulting in unbalanced colors and inaccurate hues.",
    526             [GB_COLOR_CORRECTION_CORRECT_CURVES] = @"Colors have their brightness corrected, but hues remain unbalanced.",
    527             [GB_COLOR_CORRECTION_MODERN_BALANCED] = @"Emulates a modern display. Blue contrast is moderately enhanced at the cost of slight hue inaccuracy.",
    528             [GB_COLOR_CORRECTION_MODERN_BOOST_CONTRAST] = @"Like Modern – Balanced, but further boosts the contrast of greens and magentas that is lacking on the original hardware.",
    529             [GB_COLOR_CORRECTION_REDUCE_CONTRAST] = @"Slightly reduce the contrast to better represent the tint and contrast of the original display.",
    530             [GB_COLOR_CORRECTION_LOW_CONTRAST] = @"Harshly reduce the contrast to accurately represent the tint and low contrast of the original display.",
    531             [GB_COLOR_CORRECTION_MODERN_ACCURATE] = @"Emulates a modern display. Colors have their hues and brightness corrected.",
    532          }) [self.colorCorrectionPopupButton.selectedItem.tag]
    533                                    title:self.colorCorrectionPopupButton.selectedItem.title
    534                                    onView:sender
    535                                   timeout:6
    536                             preferredEdge:NSRectEdgeMaxX];
    537 }
    538 
    539 - (IBAction)displayHighPassHelp:(id)sender
    540 {
    541     [GBWarningPopover popoverWithContents:
    542     GB_inline_const(NSString *[], {
    543         [GB_HIGHPASS_OFF] = @"No high-pass filter will be applied. DC offset will be kept, pausing and resuming will trigger audio pops.",
    544         [GB_HIGHPASS_ACCURATE] = @"An accurate high-pass filter will be applied, removing the DC offset while somewhat attenuating the bass.",
    545         [GB_HIGHPASS_REMOVE_DC_OFFSET] = @"A high-pass filter will be applied to the DC offset itself, removing the DC offset while preserving the waveform.",
    546     }) [self.highpassFilterPopupButton.selectedItem.tag]
    547                                     title:self.highpassFilterPopupButton.selectedItem.title
    548                                    onView:sender
    549                                   timeout:6
    550                             preferredEdge:NSRectEdgeMaxX];
    551 }
    552 
    553 - (IBAction)arrangeJoyCons:(id)sender
    554 {
    555     [GBJoyConManager sharedInstance].arrangementMode = true;
    556     [self beginSheet:self.joyconsSheet completionHandler:nil];
    557 }
    558 
    559 - (IBAction)closeJoyConsSheet:(id)sender
    560 {
    561     [self endSheet:self.joyconsSheet];
    562     [GBJoyConManager sharedInstance].arrangementMode = false;
    563 }
    564 
    565 - (IBAction)turboCapToggled:(NSButton *)sender
    566 {
    567     if (sender.state) {
    568         _turboCapSlider.enabled = true;
    569         [self turboCapChanged:_turboCapSlider];
    570         if (@available(macOS 10.10, *)) {
    571             _turboCapLabel.textColor = [NSColor labelColor];
    572         }
    573         else {
    574             _turboCapLabel.textColor = [NSColor blackColor];
    575         }
    576     }
    577     else {
    578         _turboCapSlider.enabled = false;
    579         _turboCapLabel.enabled = false;
    580         [[NSUserDefaults standardUserDefaults] setDouble:0 forKey:@"GBTurboCap"];
    581         if (@available(macOS 10.10, *)) {
    582             _turboCapLabel.textColor = [NSColor disabledControlTextColor];
    583         }
    584         else {
    585             _turboCapLabel.textColor = [NSColor colorWithWhite:0 alpha:0.25];
    586         }
    587     }
    588 }
    589 
    590 - (IBAction)turboCapChanged:(NSSlider *)sender
    591 {
    592     _turboCapLabel.stringValue = [NSString stringWithFormat:@"%d%%", sender.intValue];
    593     [[NSUserDefaults standardUserDefaults] setDouble:sender.doubleValue / 100.0 forKey:@"GBTurboCap"];
    594 }
    595 
    596 @end

This webpage is intended to be an accessible preview of this repository. To get a fuller picture, clone it and use the git CLI.