/* RetroArch - A frontend for libretro. * Copyright (C) 2013 - Jason Fetters * * RetroArch 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 Found- * ation, either version 3 of the License, or (at your option) any later version. * * RetroArch 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 RetroArch. * If not, see . */ #import "apple/common/RetroArch_Apple.h" #import "views.h" #include "apple/common/apple_input.h" #include "apple/common/keycode.h" #include "bluetooth/btdynamic.h" #include "bluetooth/btpad.h" static const void* const associated_userdata_key = &associated_userdata_key; static const void* const associated_module_key = &associated_module_key; static const void* const associated_setting_key = &associated_setting_key; enum SettingTypes { BooleanSetting, ButtonSetting, EnumerationSetting, FileListSetting, GroupSetting, AspectSetting, RangeSetting, CustomAction }; @interface RASettingData : NSObject { @public enum SettingTypes type; NSString* label; // < The label displayed in the settings menu NSString* name; // < The key name of the value in the config file NSString* value; // < The current state of the setting uint32_t player; // < The player that a ButtonSetting represents NSString* button_bind; // < The Gamepad button binding string NSString* axis_bind; // < The Gamepad axis binding string NSString* path; // < The base path for FileListSettings NSArray* subValues; // < The available options for EnumerationSettings and FileListSettings bool haveNoneOption; // < Determines if a 'None' option is added to an Enumeration or FileList bool haveDescriptions; // < Determines if subValues containts friendly descriptions for each value double rangeMin; // < The mininum value of a range setting double rangeMax; // < The maximum value of a range setting void (*changed)(RASettingData* action); void (*reload)(RASettingData* action, id userdata); } - (void)setValue:(NSString*)aValue; @end @implementation RASettingData - (id)initWithType:(enum SettingTypes)aType label:(NSString*)aLabel name:(NSString*)aName { type = aType; label = aLabel; name = aName; return self; } - (void)setValue:(NSString*)aValue; { value = aValue; if (changed) changed(self); } - (uint32_t)enumerationCount { return subValues.count / (haveDescriptions ? 2 : 1); } - (NSString*)valueForEnumerationIndex:(uint32_t)index { return subValues[index * (haveDescriptions ? 2 : 1)]; } - (NSString*)labelForEnumerationIndex:(uint32_t)index { return subValues[index * (haveDescriptions ? 2 : 1) + (haveDescriptions ? 1 : 0)]; } - (NSString*)labelForEnumerationValue { const uint32_t count = self.enumerationCount; for (int i = 0; haveDescriptions && i < count; i ++) { if ([value isEqualToString:subValues[i * 2]]) return subValues[i * 2 + 1]; } return value; } @end // Helper view definitions @interface RAButtonGetter : NSObject - (id)initFromTable:(UITableView*)table; - (void)runForSetting:(RASettingData*)setting; @end @interface RASettingEnumerationList : UITableViewController - (id)initWithSetting:(RASettingData*)setting fromTable:(UITableView*)table; @end static RASettingData* boolean_setting(config_file_t* config, NSString* name, NSString* label, NSString* defaultValue) { RASettingData* result = [[RASettingData alloc] initWithType:BooleanSetting label:label name:name]; result->value = objc_get_value_from_config(config, name, defaultValue); return result; } static RASettingData* button_setting(config_file_t* config, uint32_t player, NSString* name, NSString* label, NSString* defaultValue) { NSString* realname = player ? [NSString stringWithFormat:@"input_player%d_%@", player, name] : name; RASettingData* result = [[RASettingData alloc] initWithType:ButtonSetting label:label name:realname]; result->player = player ? player - 1 : 0; result->value = objc_get_value_from_config(config, realname, defaultValue); result->button_bind = objc_get_value_from_config(config, [realname stringByAppendingString:@"_btn"], @"nul"); result->axis_bind = objc_get_value_from_config(config, [realname stringByAppendingString:@"_axis"], @"nul"); return result; } static RASettingData* group_setting(NSString* label, NSArray* settings) { RASettingData* result = [[RASettingData alloc] initWithType:GroupSetting label:label name:nil]; result->subValues = settings; return result; } static RASettingData* enumeration_setting(config_file_t* config, NSString* name, NSString* label, NSString* defaultValue, NSArray* values, bool haveDescriptions) { RASettingData* result = [[RASettingData alloc] initWithType:EnumerationSetting label:label name:name]; result->value = objc_get_value_from_config(config, name, defaultValue); result->subValues = values; result->haveDescriptions = haveDescriptions; return result; } static RASettingData* subpath_setting(config_file_t* config, NSString* name, NSString* label, NSString* defaultValue, NSString* path, NSString* extension) { NSString* value = objc_get_value_from_config(config, name, defaultValue); value = [value stringByReplacingOccurrencesOfString:path withString:@""]; NSArray* values = [[NSFileManager defaultManager] subpathsOfDirectoryAtPath:path error:nil]; values = [values pathsMatchingExtensions:[NSArray arrayWithObject:extension]]; RASettingData* result = [[RASettingData alloc] initWithType:FileListSetting label:label name:name]; result->value = value; result->subValues = values; result->path = path; result->haveNoneOption = true; return result; } static RASettingData* range_setting(config_file_t* config, NSString* name, NSString* label, NSString* defaultValue, double minValue, double maxValue) { RASettingData* result = [[RASettingData alloc] initWithType:RangeSetting label:label name:name]; result->value = objc_get_value_from_config(config, name, defaultValue); result->rangeMin = minValue; result->rangeMax = maxValue; return result; } static RASettingData* aspect_setting(config_file_t* config, NSString* label) { // Why does this need to be so difficult? RASettingData* result = [[RASettingData alloc] initWithType:AspectSetting label:label name:@""]; result->subValues = [NSArray arrayWithObjects:@"Fill Screen", @"Game Aspect", @"Pixel Aspect", @"4:3", @"16:9", nil]; bool videoForceAspect = true; bool videoAspectAuto = false; double videoAspect = -1.0; if (config) { config_get_bool(config, "video_force_aspect", &videoForceAspect); config_get_bool(config, "video_aspect_auto", &videoAspectAuto); config_get_double(config, "video_aspect_ratio", &videoAspect); } if (!videoForceAspect) result->value = @"Fill Screen"; else if (videoAspect < 0.0) result->value = videoAspectAuto ? @"Game Aspect" : @"Pixel Aspect"; else result->value = (videoAspect < 1.5) ? @"4:3" : @"16:9"; return result; } static RASettingData* custom_action(NSString* action, NSString* value, id data, void (*reload_func)(RASettingData* action, id userdata)) { RASettingData* result = [[RASettingData alloc] initWithType:CustomAction label:action name:nil]; result->value = value; result->reload = reload_func; if (data != nil) objc_setAssociatedObject(result, associated_userdata_key, data, OBJC_ASSOCIATION_RETAIN_NONATOMIC); return result; } // This adds a change notify function to a setting and returns it; done this way so it can be used in NSArray // init lists. static RASettingData* change_notify(RASettingData* setting, void (*change_func)(RASettingData* setting)) { setting->changed = change_func; return setting; } static NSArray* build_input_port_group(config_file_t* config, uint32_t player) { // Only player 1 should have default key bindings #define DEFKEY(val) (player == 1) ? val : @"nul" return [NSArray arrayWithObjects: [NSArray arrayWithObjects:[NSString stringWithFormat:@"Player %d", player], button_setting(config, player, @"up", @"Up", DEFKEY(@"up")), button_setting(config, player, @"down", @"Down", DEFKEY(@"down")), button_setting(config, player, @"left", @"Left", DEFKEY(@"left")), button_setting(config, player, @"right", @"Right", DEFKEY(@"right")), button_setting(config, player, @"start", @"Start", DEFKEY(@"enter")), button_setting(config, player, @"select", @"Select", DEFKEY(@"rshift")), button_setting(config, player, @"b", @"B", DEFKEY(@"z")), button_setting(config, player, @"a", @"A", DEFKEY(@"x")), button_setting(config, player, @"x", @"X", DEFKEY(@"s")), button_setting(config, player, @"y", @"Y", DEFKEY(@"a")), button_setting(config, player, @"l", @"L", DEFKEY(@"q")), button_setting(config, player, @"r", @"R", DEFKEY(@"w")), button_setting(config, player, @"l2", @"L2", @"nul"), button_setting(config, player, @"r2", @"R2", @"nul"), button_setting(config, player, @"l3", @"L3", @"nul"), button_setting(config, player, @"r3", @"R3", @"nul"), button_setting(config, player, @"l_y_minus", @"Left Stick Up", @"nul"), button_setting(config, player, @"l_y_plus", @"Left Stick Down", @"nul"), button_setting(config, player, @"l_x_minus", @"Left Stick Left", @"nul"), button_setting(config, player, @"l_x_plus", @"Left Stick Right", @"nul"), button_setting(config, player, @"r_y_minus", @"Right Stick Up", @"nul"), button_setting(config, player, @"r_y_plus", @"Right Stick Down", @"nul"), button_setting(config, player, @"r_x_minus", @"Right Stick Left", @"nul"), button_setting(config, player, @"r_x_plus", @"Right Stick Right", @"nul"), nil], nil]; } @implementation RASettingsList { RAModuleInfo* _module; NSString* _configPath; bool _cancelSave; // Set to prevent dealloc from writing to disk } + (void)refreshModuleConfig:(RAModuleInfo*)module; { (void)[[RASettingsList alloc] initWithModule:module]; } - (id)initWithModule:(RAModuleInfo*)module { _module = module; _configPath = _module ? _module.configFile : apple_platform.globalConfigFile; config_file_t* config = config_file_new([_configPath UTF8String]); NSString* overlay_path = [[[NSBundle mainBundle] bundlePath] stringByAppendingString:@"/overlays/"]; NSString* shader_path = [[[NSBundle mainBundle] bundlePath] stringByAppendingString:@"/shaders_glsl/"]; NSArray* settings = [NSArray arrayWithObjects: [NSArray arrayWithObjects:@"Core", custom_action(@"Core Info", nil, nil, 0), _module.hasCustomConfig ? custom_action(@"Delete Custom Config", nil, nil, 0) : nil, nil], [NSArray arrayWithObjects:@"Video", boolean_setting(config, @"video_smooth", @"Bilinear filtering", @"true"), boolean_setting(config, @"video_crop_overscan", @"Crop Overscan", @"true"), boolean_setting(config, @"video_scale_integer", @"Integer Scaling", @"false"), aspect_setting(config, @"Aspect Ratio"), nil], [NSArray arrayWithObjects:@"GPU Shader", boolean_setting(config, @"video_shader_enable", @"Enable Shader", @"false"), subpath_setting(config, @"video_shader", @"Shader", @"", shader_path, @"glsl"), nil], [NSArray arrayWithObjects:@"Audio", boolean_setting(config, @"audio_enable", @"Enable Output", @"true"), boolean_setting(config, @"audio_sync", @"Sync on Audio", @"true"), boolean_setting(config, @"audio_rate_control", @"Rate Control", @"true"), nil], [NSArray arrayWithObjects:@"Input", subpath_setting(config, @"input_overlay", @"Input Overlay", @"", overlay_path, @"cfg"), range_setting(config, @"input_overlay_opacity", @"Overlay Opacity", @"1.0", 0.0, 1.0), group_setting(@"System Keys", [NSArray arrayWithObjects: // TODO: Many of these strings will be cut off on an iPhone [NSArray arrayWithObjects:@"System Keys", button_setting(config, 0, @"input_menu_toggle", @"Show RGUI", @"f1"), button_setting(config, 0, @"input_disk_eject_toggle", @"Insert/Eject Disk", @"nul"), button_setting(config, 0, @"input_disk_next", @"Cycle Disks", @"nul"), button_setting(config, 0, @"input_save_state", @"Save State", @"f2"), button_setting(config, 0, @"input_load_state", @"Load State", @"f4"), button_setting(config, 0, @"input_state_slot_increase", @"Next State Slot", @"f7"), button_setting(config, 0, @"input_state_slot_decrease", @"Previous State Slot", @"f6"), button_setting(config, 0, @"input_toggle_fast_forward", @"Toggle Fast Forward", @"space"), button_setting(config, 0, @"input_hold_fast_forward", @"Hold Fast Forward", @"l"), button_setting(config, 0, @"input_rewind", @"Rewind", @"r"), button_setting(config, 0, @"input_slowmotion", @"Slow Motion", @"e"), button_setting(config, 0, @"input_reset", @"Reset", @"h"), button_setting(config, 0, @"input_exit_emulator", @"Close Game", @"escape"), button_setting(config, 0, @"input_enable_hotkey", @"Hotkey Enable (Always on if not set)", @"nul"), nil], nil]), group_setting(@"Player 1 Keys", build_input_port_group(config, 1)), group_setting(@"Player 2 Keys", build_input_port_group(config, 2)), group_setting(@"Player 3 Keys", build_input_port_group(config, 3)), group_setting(@"Player 4 Keys", build_input_port_group(config, 4)), nil], [NSArray arrayWithObjects:@"Save States", boolean_setting(config, @"rewind_enable", @"Enable Rewinding", @"false"), boolean_setting(config, @"block_sram_overwrite", @"Disable SRAM on Load", @"false"), boolean_setting(config, @"savestate_auto_save", @"Auto Save on Exit", @"false"), boolean_setting(config, @"savestate_auto_load", @"Auto Load on Startup", @"true"), nil], nil ]; if (config) config_file_free(config); self = [super initWithSettings:settings title:_module ? _module.description : @"Global Core Config"]; return self; } - (void)dealloc { if (!_cancelSave) { config_file_t* config = config_file_new([_configPath UTF8String]); if (!config) config = config_file_new(0); if (config) { config_set_string(config, "system_directory", [[RetroArch_iOS get].systemDirectory UTF8String]); config_set_string(config, "savefile_directory", [[RetroArch_iOS get].systemDirectory UTF8String]); config_set_string(config, "savestate_directory", [[RetroArch_iOS get].systemDirectory UTF8String]); [self writeSettings:nil toConfig:config]; config_file_write(config, [_configPath UTF8String]); config_file_free(config); } apple_refresh_config(); } } - (void)handleCustomAction:(RASettingData*)setting { if ([@"Core Info" isEqualToString:setting->label]) [self.navigationController pushViewController:[[RAModuleInfoList alloc] initWithModuleInfo:_module] animated:YES]; else if([@"Delete Custom Config" isEqualToString:setting->label]) { [_module deleteCustomConfig]; _cancelSave = true; [self.navigationController popViewControllerAnimated:YES]; } } @end #pragma mark System Settings static void reload_core_config_state(RASettingData* action, RAModuleInfo* userdata) { [action setValue:userdata.hasCustomConfig ? @"[Custom]" : @"[Global]"]; } static void bluetooth_option_changed(RASettingData* setting) { ios_set_bluetooth_mode(setting->value); } @implementation RASystemSettingsList - (id)init { config_file_t* config = config_file_new([[RetroArch_iOS get].systemConfigPath UTF8String]); NSMutableArray* modules = [NSMutableArray array]; [modules addObject:@"Cores"]; [modules addObject:custom_action(@"Global Core Config", nil, nil, 0)]; NSArray* moduleList = apple_get_modules(); for (RAModuleInfo* i in moduleList) { [modules addObject:custom_action(i.description, nil, i, reload_core_config_state)]; } NSArray* bluetoothOptions = nil; if (!is_ios_7() && btstack_try_load()) bluetoothOptions = @[@"keyboard", @"Keyboard", @"icade", @"iCade Device", @"btstack", @"WiiMote/SixAxis (BTstack)"]; else if (!is_ios_7()) bluetoothOptions = @[@"keyboard", @"Keyboard", @"icade", @"iCade Device"]; else // if (is_ios_7()) bluetoothOptions = @[@"none", @"None", @"icade", @"iCade Device"]; NSArray* settings = [NSArray arrayWithObjects: [NSArray arrayWithObjects:@"Frontend", custom_action(@"Diagnostic Log", nil, nil, 0), boolean_setting(config, @"ios_tv_mode", @"TV Mode", @"false"), nil], [NSArray arrayWithObjects:@"Bluetooth", change_notify(enumeration_setting(config, @"ios_btmode", @"Mode", @"keyboard", bluetoothOptions, true), bluetooth_option_changed), nil], [NSArray arrayWithObjects:@"Orientations", boolean_setting(config, @"ios_allow_portrait", @"Portrait", @"true"), boolean_setting(config, @"ios_allow_portrait_upside_down", @"Portrait Upside Down", @"true"), boolean_setting(config, @"ios_allow_landscape_left", @"Landscape Left", @"true"), boolean_setting(config, @"ios_allow_landscape_right", @"Landscape Right", @"true"), nil], modules, nil ]; if (config) config_file_free(config); self = [super initWithSettings:settings title:@"RetroArch Settings"]; return self; } - (void)viewDidAppear:(BOOL)animated { [self.tableView reloadData]; [super viewDidAppear:animated]; } - (void)dealloc { config_file_t* config = config_file_new([[RetroArch_iOS get].systemConfigPath UTF8String]); if (!config) config = config_file_new(0); if (config) { [self writeSettings:nil toConfig:config]; config_file_write(config, [[RetroArch_iOS get].systemConfigPath UTF8String]); config_file_free(config); } [[RetroArch_iOS get] refreshSystemConfig]; } - (void)handleCustomAction:(RASettingData*)setting { if ([@"Diagnostic Log" isEqualToString:setting->label]) [self.navigationController pushViewController:[RALogView new] animated:YES]; else if ([@"Enable BTstack" isEqualToString:setting->label]) btstack_set_poweron([setting->value isEqualToString:@"true"]); else if([@"Global Core Config" isEqualToString:setting->label]) [self.navigationController pushViewController:[[RASettingsList alloc] initWithModule:nil] animated:YES]; else { RAModuleInfo* data = (RAModuleInfo*)objc_getAssociatedObject(setting, associated_userdata_key); if (data) { if (!data.hasCustomConfig) { UIAlertView* alert = [[UIAlertView alloc] initWithTitle:@"RetroArch" message:@"No custom configuration for this core exists, " "would you like to create one?" delegate:self cancelButtonTitle:@"No" otherButtonTitles:@"Yes", nil]; objc_setAssociatedObject(alert, associated_module_key, data, OBJC_ASSOCIATION_RETAIN_NONATOMIC); [alert show]; } else [self.navigationController pushViewController:[[RASettingsList alloc] initWithModule:data] animated:YES]; } } } - (void)alertView:(UIAlertView*)alertView willDismissWithButtonIndex:(NSInteger)buttonIndex { RAModuleInfo* data = (RAModuleInfo*)objc_getAssociatedObject(alertView, associated_module_key); if (data) { if (buttonIndex == alertView.firstOtherButtonIndex) { [data createCustomConfig]; [self.tableView reloadData]; } [self.navigationController pushViewController:[[RASettingsList alloc] initWithModule:data] animated:YES]; } } @end @implementation RASettingsSubList { RAButtonGetter* _binder; } - (id)initWithSettings:(NSMutableArray*)values title:(NSString*)title { self = [super initWithStyle:UITableViewStyleGrouped]; [self setTitle:title]; self.sections = values; _binder = [[RAButtonGetter alloc] initFromTable:self.tableView]; return self; } - (bool)isSettingsView { return true; } - (void)handleCustomAction:(RASettingData*)setting { } - (void)writeSettings:(NSArray*)settingList toConfig:(config_file_t*)config { if (!config) return; NSArray* list = settingList ? settingList : self.sections; for (int i = 0; i < [list count]; i++) { NSArray* group = [list objectAtIndex:i]; for (int j = 1; j < [group count]; j ++) { RASettingData* setting = [group objectAtIndex:j]; switch (setting->type) { case GroupSetting: [self writeSettings:setting->subValues toConfig:config]; break; case FileListSetting: if ([setting->value length] > 0) config_set_string(config, [setting->name UTF8String], [[setting->path stringByAppendingPathComponent:setting->value] UTF8String]); else config_set_string(config, [setting->name UTF8String], ""); break; case ButtonSetting: if (setting->value) config_set_string(config, [setting->name UTF8String], [setting->value UTF8String]); if (setting->button_bind) config_set_string(config, [[setting->name stringByAppendingString:@"_btn"] UTF8String], [setting->button_bind UTF8String]); if (setting->axis_bind) config_set_string(config, [[setting->name stringByAppendingString:@"_axis"] UTF8String], [setting->axis_bind UTF8String]); break; case AspectSetting: config_set_string(config, "video_force_aspect", [@"Fill Screen" isEqualToString:setting->value] ? "false" : "true"); config_set_string(config, "video_aspect_ratio_auto", [@"Game Aspect" isEqualToString:setting->value] ? "true" : "false"); config_set_string(config, "video_aspect_ratio", "-1.0"); if([@"4:3" isEqualToString:setting->value]) config_set_string(config, "video_aspect_ratio", "1.33333333"); else if([@"16:9" isEqualToString:setting->value]) config_set_string(config, "video_aspect_ratio", "1.77777777"); break; case CustomAction: break; default: config_set_string(config, [setting->name UTF8String], [setting->value UTF8String]); break; } } } } - (void)tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { RASettingData* setting = (RASettingData*)[self itemForIndexPath:indexPath]; switch (setting->type) { case EnumerationSetting: case FileListSetting: case AspectSetting: [self.navigationController pushViewController:[[RASettingEnumerationList alloc] initWithSetting:setting fromTable:(UITableView*)self.view] animated:YES]; break; case ButtonSetting: [_binder runForSetting:setting]; break; case GroupSetting: [self.navigationController pushViewController:[[RASettingsSubList alloc] initWithSettings:setting->subValues title:setting->label] animated:YES]; break; default: break; } [self handleCustomAction:setting]; } - (void)handleBooleanSwitch:(UISwitch*)swt { RASettingData* setting = objc_getAssociatedObject(swt, associated_setting_key); [setting setValue:swt.on ? @"true" : @"false"]; [self handleCustomAction:setting]; } - (void)handleSlider:(UISlider*)sld { RASettingData* setting = objc_getAssociatedObject(sld, associated_setting_key); [setting setValue:[NSString stringWithFormat:@"%f", sld.value]]; [self handleCustomAction:setting]; } - (UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { RASettingData* setting = (RASettingData*)[self itemForIndexPath:indexPath]; UITableViewCell* cell = nil; switch (setting->type) { case BooleanSetting: { static NSString* const cell_id = @"boolean"; cell = [self.tableView dequeueReusableCellWithIdentifier:cell_id]; if (cell == nil) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cell_id]; UISwitch* accessory = [[UISwitch alloc] init]; [accessory addTarget:self action:@selector(handleBooleanSwitch:) forControlEvents:UIControlEventValueChanged]; cell.accessoryView = accessory; [cell setSelectionStyle:UITableViewCellSelectionStyleNone]; } cell.textLabel.text = setting->label; UISwitch* swt = (UISwitch*)cell.accessoryView; swt.on = [setting->value isEqualToString:@"true"]; objc_setAssociatedObject(swt, associated_setting_key, setting, OBJC_ASSOCIATION_RETAIN_NONATOMIC); return cell; } case RangeSetting: { static NSString* const cell_id = @"range"; cell = [self.tableView dequeueReusableCellWithIdentifier:cell_id]; if (cell == nil) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cell_id]; UISlider* accessory = [UISlider new]; [accessory addTarget:self action:@selector(handleSlider:) forControlEvents:UIControlEventValueChanged]; accessory.continuous = NO; cell.accessoryView = accessory; [cell setSelectionStyle:UITableViewCellSelectionStyleNone]; } cell.textLabel.text = setting->label; UISlider* sld = (UISlider*)cell.accessoryView; sld.minimumValue = setting->rangeMin; sld.maximumValue = setting->rangeMax; sld.value = [setting->value doubleValue]; objc_setAssociatedObject(sld, associated_setting_key, setting, OBJC_ASSOCIATION_RETAIN_NONATOMIC); return cell; } break; default: { static NSString* const cell_id = @"default"; cell = [self.tableView dequeueReusableCellWithIdentifier:cell_id]; if (!cell) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:cell_id]; cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; } if (setting->reload) setting->reload(setting, objc_getAssociatedObject(setting, associated_userdata_key)); cell.textLabel.text = setting->label; if (setting->type == ButtonSetting) cell.detailTextLabel.text = [NSString stringWithFormat:@"[KB:%@] [JS:%@] [AX:%@]", [setting->value length] ? setting->value : @"nul", [setting->button_bind length] ? setting->button_bind : @"nul", [setting->axis_bind length] ? setting->axis_bind : @"nul"]; else if(setting->type == EnumerationSetting) cell.detailTextLabel.text = setting.labelForEnumerationValue; else cell.detailTextLabel.text = setting->value; return cell; } } } @end @implementation RASettingEnumerationList { RASettingData* _value; UITableView* _view; unsigned _mainSection; }; - (id)initWithSetting:(RASettingData*)setting fromTable:(UITableView*)table { self = [super initWithStyle:UITableViewStyleGrouped]; _value = setting; _view = table; _mainSection = _value->haveNoneOption ? 1 : 0; [self setTitle: _value->label]; return self; } - (NSInteger)numberOfSectionsInTableView:(UITableView*)tableView { return _value->haveNoneOption ? 2 : 1; } - (NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section { return (section == _mainSection) ? _value.enumerationCount : 1; } - (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString* const cell_id = @"option"; UITableViewCell* cell = [self.tableView dequeueReusableCellWithIdentifier:cell_id]; cell = cell ? cell : [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cell_id]; if (indexPath.section == _mainSection) cell.textLabel.text = [_value labelForEnumerationIndex:indexPath.row]; else cell.textLabel.text = @"None"; return cell; } - (void)tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { [_value setValue: (indexPath.section == _mainSection) ? [_value valueForEnumerationIndex:indexPath.row] : @""]; [_view reloadData]; [self.navigationController popViewControllerAnimated:YES]; } @end @implementation RAButtonGetter { RASettingData* _value; UIAlertView* _alert; UITableView* _view; bool _finished; NSTimer* _btTimer; } - (id)initFromTable:(UITableView*)table { self = [super init]; _view = table; _alert = [[UIAlertView alloc] initWithTitle:@"RetroArch" message:@"" delegate:self cancelButtonTitle:@"Cancel" otherButtonTitles:@"Clear Keyboard", @"Clear Joystick", @"Clear Axis", nil]; if (is_ios_7()) _alert.alertViewStyle = UIAlertViewStylePlainTextInput; return self; } - (void)runForSetting:(RASettingData*)setting { apple_input_reset_icade_buttons(); _value = setting; _alert.message = _value->name; [_alert show]; _btTimer = [NSTimer scheduledTimerWithTimeInterval:.05f target:self selector:@selector(checkInput) userInfo:nil repeats:YES]; _finished = false; } - (void)finish { if (!_finished) { _finished = true; apple_input_reset_icade_buttons(); [_btTimer invalidate]; _btTimer = nil; [_alert dismissWithClickedButtonIndex:_alert.cancelButtonIndex animated:YES]; [_view reloadData]; } } - (void)alertView:(UIAlertView*)alertView willDismissWithButtonIndex:(NSInteger)buttonIndex { if (buttonIndex == _alert.firstOtherButtonIndex) _value->value = @"nul"; else if(buttonIndex == _alert.firstOtherButtonIndex + 1) _value->button_bind = @"nul"; else if(buttonIndex == _alert.firstOtherButtonIndex + 2) _value->axis_bind = @"nul"; [self finish]; } - (void)checkInput { int32_t value = 0; if ((value = apple_input_find_any_key())) _value->value = @(apple_keycode_hidusage_to_name(value)); else if ((value = apple_input_find_any_button(0)) >= 0) _value->button_bind = [NSString stringWithFormat:@"%d", value]; else if ((value = apple_input_find_any_axis(0))) _value->axis_bind = [NSString stringWithFormat:@"%s%d", (value > 0) ? "+" : "-", value - 1]; else return; [self finish]; } @end