visualboyadvance-m/src/wx/opts.cpp

1070 lines
48 KiB
C++

#include <vector>
#include <algorithm>
#include <wx/log.h>
#include <wx/display.h>
#include "wx/gamecontrol.h"
#include "wx/userinput.h"
#include "wxvbam.h"
#include "strutils.h"
/*
disableSfx(F) -> cpuDisableSfx
priority(2) -> threadPriority
saveMoreCPU(F) -> Sm60FPS
SDL:
-p/--profile=hz
*/
#define WJKB wxUserInput::FromLegacyKeyModJoy
/* not sure how well other compilers support field-init syntax */
#define STROPT(c, n, d, v) \
new_opt_desc(wxT(c), (n), d, &v)
#define INTOPT(c, n, d, v, min, max) \
new_opt_desc(wxT(c), (n), d, NULL, &v, wxT(""), min, max)
#define DOUBLEOPT(c, n, d, v, min, max) \
new_opt_desc(wxT(c), (n), d, NULL, NULL, wxT(""), min, max, NULL, &v)
#define UINTOPT(c, n, d, v, min, max) \
new_opt_desc(wxT(c), (n), d, NULL, NULL, wxT(""), min, max, NULL, NULL, &v)
#define BOOLOPT(c, n, d, v) \
new_opt_desc(wxT(c), (n), d, NULL, NULL, wxT(""), 0, 0, &v)
#define ENUMOPT(c, n, d, v, e) \
new_opt_desc(wxT(c), (n), d, NULL, &v, e)
#define NOOPT(c, n, d) \
new_opt_desc(c, (n), d)
opts_t gopts;
// having the standard menu accels here means they will work even without menus
const wxAcceleratorEntryUnicode default_accels[] = {
wxAcceleratorEntryUnicode(wxMOD_CMD, wxT('C'), XRCID("CheatsList")),
wxAcceleratorEntryUnicode(wxMOD_CMD, wxT('N'), XRCID("NextFrame")),
// some ports add ctrl-q anyway, so may as well make it official
// maybe make alt-f4 universal as well...
// FIXME: ctrl-Q does not work on wxMSW
// FIXME: esc does not work on wxMSW
// this was annoying people A LOT #334
//wxAcceleratorEntry(wxMOD_NONE, WXK_ESCAPE, wxID_EXIT),
// this was annoying people #298
//wxAcceleratorEntry(wxMOD_CMD, wxT('X'), wxID_EXIT),
wxAcceleratorEntryUnicode(wxMOD_CMD, wxT('Q'), wxID_EXIT),
// FIXME: ctrl-W does not work on wxMSW
wxAcceleratorEntryUnicode(wxMOD_CMD, wxT('W'), wxID_CLOSE),
// load most recent is more commonly used than load other
//wxAcceleratorEntry(wxMOD_CMD, wxT('L'), XRCID("Load")),
wxAcceleratorEntryUnicode(wxMOD_CMD, wxT('L'), XRCID("LoadGameRecent")),
wxAcceleratorEntryUnicode(wxMOD_NONE, WXK_F1, XRCID("LoadGame01")),
wxAcceleratorEntryUnicode(wxMOD_NONE, WXK_F2, XRCID("LoadGame02")),
wxAcceleratorEntryUnicode(wxMOD_NONE, WXK_F3, XRCID("LoadGame03")),
wxAcceleratorEntryUnicode(wxMOD_NONE, WXK_F4, XRCID("LoadGame04")),
wxAcceleratorEntryUnicode(wxMOD_NONE, WXK_F5, XRCID("LoadGame05")),
wxAcceleratorEntryUnicode(wxMOD_NONE, WXK_F6, XRCID("LoadGame06")),
wxAcceleratorEntryUnicode(wxMOD_NONE, WXK_F7, XRCID("LoadGame07")),
wxAcceleratorEntryUnicode(wxMOD_NONE, WXK_F8, XRCID("LoadGame08")),
wxAcceleratorEntryUnicode(wxMOD_NONE, WXK_F9, XRCID("LoadGame09")),
wxAcceleratorEntryUnicode(wxMOD_NONE, WXK_F10, XRCID("LoadGame10")),
wxAcceleratorEntryUnicode(wxMOD_NONE, WXK_PAUSE, XRCID("Pause")),
wxAcceleratorEntryUnicode(wxMOD_CMD, wxT('P'), XRCID("Pause")),
wxAcceleratorEntryUnicode(wxMOD_CMD, wxT('R'), XRCID("Reset")),
// add shortcuts for original size multiplier #415
wxAcceleratorEntryUnicode(wxMOD_NONE, wxT('1'), XRCID("SetSize1x")),
wxAcceleratorEntryUnicode(wxMOD_NONE, wxT('2'), XRCID("SetSize2x")),
wxAcceleratorEntryUnicode(wxMOD_NONE, wxT('3'), XRCID("SetSize3x")),
wxAcceleratorEntryUnicode(wxMOD_NONE, wxT('4'), XRCID("SetSize4x")),
wxAcceleratorEntryUnicode(wxMOD_NONE, wxT('5'), XRCID("SetSize5x")),
wxAcceleratorEntryUnicode(wxMOD_NONE, wxT('6'), XRCID("SetSize6x")),
// save oldest is more commonly used than save other
//wxAcceleratorEntry(wxMOD_CMD, wxT('S'), XRCID("Save")),
wxAcceleratorEntryUnicode(wxMOD_CMD, wxT('S'), XRCID("SaveGameOldest")),
wxAcceleratorEntryUnicode(wxMOD_SHIFT, WXK_F1, XRCID("SaveGame01")),
wxAcceleratorEntryUnicode(wxMOD_SHIFT, WXK_F2, XRCID("SaveGame02")),
wxAcceleratorEntryUnicode(wxMOD_SHIFT, WXK_F3, XRCID("SaveGame03")),
wxAcceleratorEntryUnicode(wxMOD_SHIFT, WXK_F4, XRCID("SaveGame04")),
wxAcceleratorEntryUnicode(wxMOD_SHIFT, WXK_F5, XRCID("SaveGame05")),
wxAcceleratorEntryUnicode(wxMOD_SHIFT, WXK_F6, XRCID("SaveGame06")),
wxAcceleratorEntryUnicode(wxMOD_SHIFT, WXK_F7, XRCID("SaveGame07")),
wxAcceleratorEntryUnicode(wxMOD_SHIFT, WXK_F8, XRCID("SaveGame08")),
wxAcceleratorEntryUnicode(wxMOD_SHIFT, WXK_F9, XRCID("SaveGame09")),
wxAcceleratorEntryUnicode(wxMOD_SHIFT, WXK_F10, XRCID("SaveGame10")),
// I prefer the SDL ESC key binding
//wxAcceleratorEntry(wxMOD_NONE, WXK_ESCAPE, XRCID("ToggleFullscreen"),
// alt-enter is more standard anyway
wxAcceleratorEntryUnicode(wxMOD_ALT, WXK_RETURN, XRCID("ToggleFullscreen")),
wxAcceleratorEntryUnicode(wxMOD_ALT, wxT('1'), XRCID("JoypadAutofireA")),
wxAcceleratorEntryUnicode(wxMOD_ALT, wxT('2'), XRCID("JoypadAutofireB")),
wxAcceleratorEntryUnicode(wxMOD_ALT, wxT('3'), XRCID("JoypadAutofireL")),
wxAcceleratorEntryUnicode(wxMOD_ALT, wxT('4'), XRCID("JoypadAutofireR")),
wxAcceleratorEntryUnicode(wxMOD_CMD, wxT('1'), XRCID("VideoLayersBG0")),
wxAcceleratorEntryUnicode(wxMOD_CMD, wxT('2'), XRCID("VideoLayersBG1")),
wxAcceleratorEntryUnicode(wxMOD_CMD, wxT('3'), XRCID("VideoLayersBG2")),
wxAcceleratorEntryUnicode(wxMOD_CMD, wxT('4'), XRCID("VideoLayersBG3")),
wxAcceleratorEntryUnicode(wxMOD_CMD, wxT('5'), XRCID("VideoLayersOBJ")),
wxAcceleratorEntryUnicode(wxMOD_CMD, wxT('6'), XRCID("VideoLayersWIN0")),
wxAcceleratorEntryUnicode(wxMOD_CMD, wxT('7'), XRCID("VideoLayersWIN1")),
wxAcceleratorEntryUnicode(wxMOD_CMD, wxT('8'), XRCID("VideoLayersOBJWIN")),
wxAcceleratorEntryUnicode(wxMOD_CMD, wxT('B'), XRCID("Rewind")),
// following are not in standard menus
// FILExx are filled in when recent menu is filled
wxAcceleratorEntryUnicode(wxMOD_CMD, WXK_F1, wxID_FILE1),
wxAcceleratorEntryUnicode(wxMOD_CMD, WXK_F2, wxID_FILE2),
wxAcceleratorEntryUnicode(wxMOD_CMD, WXK_F3, wxID_FILE3),
wxAcceleratorEntryUnicode(wxMOD_CMD, WXK_F4, wxID_FILE4),
wxAcceleratorEntryUnicode(wxMOD_CMD, WXK_F5, wxID_FILE5),
wxAcceleratorEntryUnicode(wxMOD_CMD, WXK_F6, wxID_FILE6),
wxAcceleratorEntryUnicode(wxMOD_CMD, WXK_F7, wxID_FILE7),
wxAcceleratorEntryUnicode(wxMOD_CMD, WXK_F8, wxID_FILE8),
wxAcceleratorEntryUnicode(wxMOD_CMD, WXK_F9, wxID_FILE9),
wxAcceleratorEntryUnicode(wxMOD_CMD, WXK_F10, wxID_FILE10),
wxAcceleratorEntryUnicode(wxMOD_CMD, wxT('0'), XRCID("VideoLayersReset")),
wxAcceleratorEntryUnicode(wxMOD_CMD, wxT('G'), XRCID("ChangeFilter")),
wxAcceleratorEntryUnicode(wxMOD_NONE, WXK_NUMPAD_ADD, XRCID("IncreaseVolume")),
wxAcceleratorEntryUnicode(wxMOD_NONE, WXK_NUMPAD_SUBTRACT, XRCID("DecreaseVolume")),
wxAcceleratorEntryUnicode(wxMOD_NONE, WXK_NUMPAD_ENTER, XRCID("ToggleSound"))
};
const int num_def_accels = sizeof(default_accels) / sizeof(default_accels[0]);
const std::map<wxGameControl, std::set<wxUserInput>> kDefaultBindings = {
{ wxGameControl(0, wxGameKey::Up), {
WJKB(wxT('W')),
WJKB(11, wxJoyControl::Button, 1),
WJKB(1, wxJoyControl::AxisMinus, 1),
WJKB(3, wxJoyControl::AxisMinus, 1),
}},
{ wxGameControl(0, wxGameKey::Down), {
WJKB(wxT('S')),
WJKB(12, wxJoyControl::Button, 1),
WJKB(1, wxJoyControl::AxisPlus, 1),
WJKB(3, wxJoyControl::AxisPlus, 1),
}},
{ wxGameControl(0, wxGameKey::Left), {
WJKB(wxT('A')),
WJKB(13, wxJoyControl::Button, 1),
WJKB(0, wxJoyControl::AxisMinus, 1),
WJKB(2, wxJoyControl::AxisMinus, 1),
}},
{ wxGameControl(0, wxGameKey::Right), {
WJKB(wxT('D')),
WJKB(14, wxJoyControl::Button, 1),
WJKB(0, wxJoyControl::AxisPlus, 1),
WJKB(2, wxJoyControl::AxisPlus, 1),
}},
{ wxGameControl(0, wxGameKey::A), {
WJKB(wxT('L')),
WJKB(0, wxJoyControl::Button, 1),
}},
{ wxGameControl(0, wxGameKey::B), {
WJKB(wxT('K')),
WJKB(1, wxJoyControl::Button, 1),
}},
{ wxGameControl(0, wxGameKey::L), {
WJKB(wxT('I')),
WJKB(2, wxJoyControl::Button, 1),
WJKB(9, wxJoyControl::Button, 1),
WJKB(4, wxJoyControl::AxisPlus, 1),
}},
{ wxGameControl(0, wxGameKey::R), {
WJKB(wxT('O')),
WJKB(3, wxJoyControl::Button, 1),
WJKB(10, wxJoyControl::Button, 1),
WJKB(5, wxJoyControl::AxisPlus, 1),
}},
{ wxGameControl(0, wxGameKey::Select), {
WJKB(WXK_BACK),
WJKB(4, wxJoyControl::Button, 1),
}},
{ wxGameControl(0, wxGameKey::Start), {
WJKB(WXK_RETURN),
WJKB(6, wxJoyControl::Button, 1),
}},
{ wxGameControl(0, wxGameKey::MotionUp), {}},
{ wxGameControl(0, wxGameKey::MotionDown), {}},
{ wxGameControl(0, wxGameKey::MotionLeft), {}},
{ wxGameControl(0, wxGameKey::MotionRight), {}},
{ wxGameControl(0, wxGameKey::MotionIn), {}},
{ wxGameControl(0, wxGameKey::MotionOut), {}},
{ wxGameControl(0, wxGameKey::AutoA), {}},
{ wxGameControl(0, wxGameKey::AutoB), {}},
{ wxGameControl(0, wxGameKey::Speed), {
WJKB(WXK_SPACE),
}},
{ wxGameControl(0, wxGameKey::Capture), {}},
{ wxGameControl(0, wxGameKey::Gameshark), {}},
{ wxGameControl(1, wxGameKey::Up), {}},
{ wxGameControl(1, wxGameKey::Down), {}},
{ wxGameControl(1, wxGameKey::Left), {}},
{ wxGameControl(1, wxGameKey::Right), {}},
{ wxGameControl(1, wxGameKey::A), {}},
{ wxGameControl(1, wxGameKey::B), {}},
{ wxGameControl(1, wxGameKey::L), {}},
{ wxGameControl(1, wxGameKey::R), {}},
{ wxGameControl(1, wxGameKey::Select), {}},
{ wxGameControl(1, wxGameKey::Start), {}},
{ wxGameControl(1, wxGameKey::MotionUp), {}},
{ wxGameControl(1, wxGameKey::MotionDown), {}},
{ wxGameControl(1, wxGameKey::MotionLeft), {}},
{ wxGameControl(1, wxGameKey::MotionRight), {}},
{ wxGameControl(1, wxGameKey::MotionIn), {}},
{ wxGameControl(1, wxGameKey::MotionOut), {}},
{ wxGameControl(1, wxGameKey::AutoA), {}},
{ wxGameControl(1, wxGameKey::AutoB), {}},
{ wxGameControl(1, wxGameKey::Speed), {}},
{ wxGameControl(1, wxGameKey::Capture), {}},
{ wxGameControl(1, wxGameKey::Gameshark), {}},
{ wxGameControl(2, wxGameKey::Up), {}},
{ wxGameControl(2, wxGameKey::Down), {}},
{ wxGameControl(2, wxGameKey::Left), {}},
{ wxGameControl(2, wxGameKey::Right), {}},
{ wxGameControl(2, wxGameKey::A), {}},
{ wxGameControl(2, wxGameKey::B), {}},
{ wxGameControl(2, wxGameKey::L), {}},
{ wxGameControl(2, wxGameKey::R), {}},
{ wxGameControl(2, wxGameKey::Select), {}},
{ wxGameControl(2, wxGameKey::Start), {}},
{ wxGameControl(2, wxGameKey::MotionUp), {}},
{ wxGameControl(2, wxGameKey::MotionDown), {}},
{ wxGameControl(2, wxGameKey::MotionLeft), {}},
{ wxGameControl(2, wxGameKey::MotionRight), {}},
{ wxGameControl(2, wxGameKey::MotionIn), {}},
{ wxGameControl(2, wxGameKey::MotionOut), {}},
{ wxGameControl(2, wxGameKey::AutoA), {}},
{ wxGameControl(2, wxGameKey::AutoB), {}},
{ wxGameControl(2, wxGameKey::Speed), {}},
{ wxGameControl(2, wxGameKey::Capture), {}},
{ wxGameControl(2, wxGameKey::Gameshark), {}},
{ wxGameControl(3, wxGameKey::Up), {}},
{ wxGameControl(3, wxGameKey::Down), {}},
{ wxGameControl(3, wxGameKey::Left), {}},
{ wxGameControl(3, wxGameKey::Right), {}},
{ wxGameControl(3, wxGameKey::A), {}},
{ wxGameControl(3, wxGameKey::B), {}},
{ wxGameControl(3, wxGameKey::L), {}},
{ wxGameControl(3, wxGameKey::R), {}},
{ wxGameControl(3, wxGameKey::Select), {}},
{ wxGameControl(3, wxGameKey::Start), {}},
{ wxGameControl(3, wxGameKey::MotionUp), {}},
{ wxGameControl(3, wxGameKey::MotionDown), {}},
{ wxGameControl(3, wxGameKey::MotionLeft), {}},
{ wxGameControl(3, wxGameKey::MotionRight), {}},
{ wxGameControl(3, wxGameKey::MotionIn), {}},
{ wxGameControl(3, wxGameKey::MotionOut), {}},
{ wxGameControl(3, wxGameKey::AutoA), {}},
{ wxGameControl(3, wxGameKey::AutoB), {}},
{ wxGameControl(3, wxGameKey::Speed), {}},
{ wxGameControl(3, wxGameKey::Capture), {}},
{ wxGameControl(3, wxGameKey::Gameshark), {}},
};
wxAcceleratorEntry_v sys_accels;
// Initializer for struct opt_desc
opt_desc new_opt_desc(wxString opt, const char* cmd, wxString desc,
wxString* stropt, int* intopt, wxString enumvals,
double min, double max, bool* boolopt,
double* doubleopt, uint32_t* uintopt, wxString curstr,
int curint, double curdouble, uint32_t curuint)
{
struct opt_desc new_opt = {opt, cmd, desc, stropt, intopt, enumvals,
min, max, boolopt, doubleopt, uintopt,
curstr, curint, curdouble, curuint};
return new_opt;
}
// Note: this table must be sorted in option name order
// Both for better user display and for (fast) searching by name
opt_desc opts[] = {
/// Display
BOOLOPT("Display/Bilinear", "Bilinear", wxTRANSLATE("Use bilinear filter with 3d renderer"), gopts.bilinear),
ENUMOPT("Display/Filter", "", wxTRANSLATE("Full-screen filter to apply"), gopts.filter,
wxTRANSLATE("none|2xsai|super2xsai|supereagle|pixelate|advmame|"
L"bilinear|bilinearplus|scanlines|tvmode|hq2x|lq2x|"
L"simple2x|simple3x|hq3x|simple4x|hq4x|xbrz2x|xbrz3x|xbrz4x|xbrz5x|xbrz6x|plugin")),
STROPT("Display/FilterPlugin", "", wxTRANSLATE("Filter plugin library"), gopts.filter_plugin),
ENUMOPT("Display/IFB", "", wxTRANSLATE("Interframe blending function"), gopts.ifb, wxTRANSLATE("none|smart|motionblur")),
BOOLOPT("Display/KeepOnTop", "KeepOnTop", wxTRANSLATE("Keep window on top"), gopts.keep_on_top),
INTOPT("Display/MaxThreads", "Multithread", wxTRANSLATE("Maximum number of threads to run filters in"), gopts.max_threads, 1, 256),
#ifdef __WXMSW__
ENUMOPT("Display/RenderMethod", "", wxTRANSLATE("Render method; if unsupported, simple method will be used"), gopts.render_method, wxTRANSLATE("simple|opengl|direct3d")),
#elif defined(__WXMAC__)
ENUMOPT("Display/RenderMethod", "", wxTRANSLATE("Render method; if unsupported, simple method will be used"), gopts.render_method, wxTRANSLATE("simple|opengl|quartz2d")),
#else
ENUMOPT("Display/RenderMethod", "", wxTRANSLATE("Render method; if unsupported, simple method will be used"), gopts.render_method, wxTRANSLATE("simple|opengl")),
#endif
DOUBLEOPT("Display/Scale", "", wxTRANSLATE("Default scale factor"), gopts.video_scale, 1, 6),
BOOLOPT("Display/Stretch", "RetainAspect", wxTRANSLATE("Retain aspect ratio when resizing"), gopts.retain_aspect),
/// GB
STROPT("GB/BiosFile", "", wxTRANSLATE("BIOS file to use for GB, if enabled"), gopts.gb_bios),
INTOPT("GB/ColorOption", "GBColorOption", wxTRANSLATE("GB color enhancement, if enabled"), gbColorOption, 0, 1),
INTOPT("GB/ColorizerHack", "ColorizerHack", wxTRANSLATE("Enable DX Colorization Hacks"), colorizerHack, 0, 1),
BOOLOPT("GB/LCDFilter", "GBLcdFilter", wxTRANSLATE("Apply LCD filter, if enabled"), gbLcdFilter),
STROPT("GB/GBCBiosFile", "", wxTRANSLATE("BIOS file to use for GBC, if enabled"), gopts.gbc_bios),
NOOPT(wxT("GB/Palette0"), "", wxTRANSLATE("The default palette, as 8 comma-separated 4-digit hex integers (rgb555).")),
NOOPT(wxT("GB/Palette1"), "", wxTRANSLATE("The first user palette, as 8 comma-separated 4-digit hex integers (rgb555).")),
NOOPT(wxT("GB/Palette2"), "", wxTRANSLATE("The second user palette, as 8 comma-separated 4-digit hex integers (rgb555).")),
BOOLOPT("GB/PrintAutoPage", "PrintGather", wxTRANSLATE("Automatically gather a full page before printing"), gopts.print_auto_page),
BOOLOPT("GB/PrintScreenCap", "PrintSnap", wxTRANSLATE("Automatically save printouts as screen captures with -print suffix"), gopts.print_screen_cap),
STROPT("GB/ROMDir", "", wxTRANSLATE("Directory to look for ROM files"), gopts.gb_rom_dir),
STROPT("GB/GBCROMDir", "", wxTRANSLATE("Directory to look for GBC ROM files"), gopts.gbc_rom_dir),
/// GBA
STROPT("GBA/BiosFile", "", wxTRANSLATE("BIOS file to use, if enabled"), gopts.gba_bios),
BOOLOPT("GBA/LCDFilter", "GBALcdFilter", wxTRANSLATE("Apply LCD filter, if enabled"), gbaLcdFilter),
#ifndef NO_LINK
BOOLOPT("GBA/LinkAuto", "LinkAuto", wxTRANSLATE("Enable link at boot"), gopts.link_auto),
INTOPT("GBA/LinkFast", "SpeedOn", wxTRANSLATE("Enable faster network protocol by default"), linkHacks, 0, 1),
STROPT("GBA/LinkHost", "", wxTRANSLATE("Default network link client host"), gopts.link_host),
STROPT("GBA/ServerIP", "", wxTRANSLATE("Default network link server IP to bind"), gopts.server_ip),
UINTOPT("GBA/LinkPort", "", wxTRANSLATE("Default network link port (server and client)"), gopts.link_port, 0, 65535),
INTOPT("GBA/LinkProto", "LinkProto", wxTRANSLATE("Default network protocol"), gopts.link_proto, 0, 1),
INTOPT("GBA/LinkTimeout", "LinkTimeout", wxTRANSLATE("Link timeout (ms)"), linkTimeout, 0, 9999999),
INTOPT("GBA/LinkType", "LinkType", wxTRANSLATE("Link cable type"), gopts.gba_link_type, 0, 5),
#endif
STROPT("GBA/ROMDir", "", wxTRANSLATE("Directory to look for ROM files"), gopts.gba_rom_dir),
/// General
BOOLOPT("General/AutoLoadLastState", "", wxTRANSLATE("Automatically load last saved state"), gopts.autoload_state),
STROPT("General/BatteryDir", "", wxTRANSLATE("Directory to store game save files (relative paths are relative to ROM; blank is config dir)"), gopts.battery_dir),
BOOLOPT("General/FreezeRecent", "", wxTRANSLATE("Freeze recent load list"), gopts.recent_freeze),
STROPT("General/RecordingDir", "", wxTRANSLATE("Directory to store A/V and game recordings (relative paths are relative to ROM)"), gopts.recording_dir),
INTOPT("General/RewindInterval", "", wxTRANSLATE("Number of seconds between rewind snapshots (0 to disable)"), gopts.rewind_interval, 0, 600),
STROPT("General/ScreenshotDir", "", wxTRANSLATE("Directory to store screenshots (relative paths are relative to ROM)"), gopts.scrshot_dir),
STROPT("General/StateDir", "", wxTRANSLATE("Directory to store saved state files (relative paths are relative to BatteryDir)"), gopts.state_dir),
INTOPT("General/StatusBar", "StatusBar", wxTRANSLATE("Enable status bar"), gopts.statusbar, 0, 1),
/// Joypad
NOOPT(wxT("Joypad/*/*"), "", wxTRANSLATE("The parameter Joypad/<n>/<button> contains a comma-separated list of key names which map to joypad #<n> button <button>. Button is one of Up, Down, Left, Right, A, B, L, R, Select, Start, MotionUp, MotionDown, MotionLeft, MotionRight, AutoA, AutoB, Speed, Capture, GS")),
INTOPT("Joypad/AutofireThrottle", "", wxTRANSLATE("The autofire toggle period, in frames (1/60 s)"), gopts.autofire_rate, 1, 1000),
/// Keyboard
INTOPT("Joypad/Default", "", wxTRANSLATE("The number of the stick to use in single-player mode"), gopts.default_stick, 1, 4),
/// Keyboard
NOOPT(wxT("Keyboard/*"), "", wxTRANSLATE("The parameter Keyboard/<cmd> contains a comma-separated list of key names (e.g. Alt-Shift-F1). When the named key is pressed, the command <cmd> is executed.")),
// Core
INTOPT("preferences/agbPrint", "AGBPrinter", wxTRANSLATE("Enable AGB debug print"), agbPrint, 0, 1),
INTOPT("preferences/autoFrameSkip", "FrameSkipAuto", wxTRANSLATE("Auto skip frames."), autoFrameSkip, 0, 1),
INTOPT("preferences/autoPatch", "ApplyPatches", wxTRANSLATE("Apply IPS/UPS/IPF patches if found"), autoPatch, 0, 1),
BOOLOPT("preferences/autoSaveLoadCheatList", "", wxTRANSLATE("Automatically save and load cheat list"), gopts.autoload_cheats),
INTOPT("preferences/borderAutomatic", "", wxTRANSLATE("Automatically enable border for Super GameBoy games"), gbBorderAutomatic, 0, 1),
INTOPT("preferences/borderOn", "", wxTRANSLATE("Always enable border"), gbBorderOn, 0, 1),
INTOPT("preferences/captureFormat", "", wxTRANSLATE("Screen capture file format"), captureFormat, 0, 1),
INTOPT("preferences/cheatsEnabled", "", wxTRANSLATE("Enable cheats"), cheatsEnabled, 0, 1),
#ifdef MMX
INTOPT("preferences/enableMMX", "MMX", wxTRANSLATE("Enable MMX"), enableMMX, 0, 1),
#endif
INTOPT("preferences/disableStatus", "NoStatusMsg", wxTRANSLATE("Disable on-screen status messages"), disableStatusMessages, 0, 1),
INTOPT("preferences/emulatorType", "", wxTRANSLATE("Type of system to emulate"), gbEmulatorType, 0, 5),
INTOPT("preferences/flashSize", "", wxTRANSLATE("Flash size 0 = 64KB 1 = 128KB"), optFlashSize, 0, 1),
INTOPT("preferences/frameSkip", "FrameSkip", wxTRANSLATE("Skip frames. Values are 0-9 or -1 to skip automatically based on time."), frameSkip, -1, 9),
INTOPT("preferences/fsColorDepth", "", wxTRANSLATE("Fullscreen mode color depth (0 = any)"), fsColorDepth, 0, 999),
INTOPT("preferences/fsFrequency", "", wxTRANSLATE("Fullscreen mode frequency (0 = any)"), fsFrequency, 0, 999),
INTOPT("preferences/fsHeight", "", wxTRANSLATE("Fullscreen mode height (0 = desktop)"), fsHeight, 0, 99999),
INTOPT("preferences/fsWidth", "", wxTRANSLATE("Fullscreen mode width (0 = desktop)"), fsWidth, 0, 99999),
INTOPT("preferences/gbPaletteOption", "", wxTRANSLATE("The palette to use"), gbPaletteOption, 0, 2),
INTOPT("preferences/gbPrinter", "Printer", wxTRANSLATE("Enable printer emulation"), winGbPrinterEnabled, 0, 1),
INTOPT("preferences/gdbBreakOnLoad", "DebugGDBBreakOnLoad", wxTRANSLATE("Break into GDB after loading the game."), gdbBreakOnLoad, 0, 1),
INTOPT("preferences/gdbPort", "DebugGDBPort", wxTRANSLATE("Port to connect GDB to."), gdbPort, 0, 65535),
#ifndef NO_LINK
INTOPT("preferences/LinkNumPlayers", "", wxTRANSLATE("Number of players in network"), linkNumPlayers, 2, 4),
#endif
INTOPT("preferences/maxScale", "", wxTRANSLATE("Maximum scale factor (0 = no limit)"), maxScale, 0, 100),
INTOPT("preferences/pauseWhenInactive", "PauseWhenInactive", wxTRANSLATE("Pause game when main window loses focus"), pauseWhenInactive, 0, 1),
INTOPT("preferences/rtcEnabled", "RTC", wxTRANSLATE("Enable RTC (vba-over.ini override is rtcEnabled"), rtcEnabled, 0, 1),
INTOPT("preferences/saveType", "", wxTRANSLATE("Native save (\"battery\") hardware type"), cpuSaveType, 0, 5),
INTOPT("preferences/showSpeed", "", wxTRANSLATE("Show speed indicator"), showSpeed, 0, 2),
INTOPT("preferences/showSpeedTransparent", "Transparent", wxTRANSLATE("Draw on-screen messages transparently"), showSpeedTransparent, 0, 1),
INTOPT("preferences/skipBios", "SkipIntro", wxTRANSLATE("Skip BIOS initialization"), skipBios, 0, 1),
INTOPT("preferences/skipSaveGameCheats", "", wxTRANSLATE("Do not overwrite cheat list when loading state"), skipSaveGameCheats, 0, 1),
INTOPT("preferences/skipSaveGameBattery", "", wxTRANSLATE("Do not overwrite native (battery) save when loading state"), skipSaveGameBattery, 0, 1),
UINTOPT("preferences/throttle", "", wxTRANSLATE("Throttle game speed, even when accelerated (0-450%, 0 = no throttle)"), throttle, 0, 450),
UINTOPT("preferences/speedupThrottle", "", wxTRANSLATE("Set throttle for speedup key (0-3000%, 0 = no throttle)"), speedup_throttle, 0, 3000),
UINTOPT("preferences/speedupFrameSkip", "", wxTRANSLATE("Number of frames to skip with speedup (instead of speedup throttle)"), speedup_frame_skip, 0, 300),
BOOLOPT("preferences/speedupThrottleFrameSkip", "", wxTRANSLATE("Use frame skip for speedup throttle"), speedup_throttle_frame_skip),
INTOPT("preferences/useBiosGB", "BootRomGB", wxTRANSLATE("Use the specified BIOS file for GB"), useBiosFileGB, 0, 1),
INTOPT("preferences/useBiosGBA", "BootRomEn", wxTRANSLATE("Use the specified BIOS file"), useBiosFileGBA, 0, 1),
INTOPT("preferences/useBiosGBC", "BootRomGBC", wxTRANSLATE("Use the specified BIOS file for GBC"), useBiosFileGBC, 0, 1),
INTOPT("preferences/vsync", "VSync", wxTRANSLATE("Wait for vertical sync"), vsync, 0, 1),
/// Geometry
INTOPT("geometry/fullScreen", "Fullscreen", wxTRANSLATE("Enter fullscreen mode at startup"), fullScreen, 0, 1),
INTOPT("geometry/isMaximized", "Maximized", wxTRANSLATE("Window maximized"), windowMaximized, 0, 1),
UINTOPT("geometry/windowHeight", "Height", wxTRANSLATE("Window height at startup"), windowHeight, 0, 99999),
UINTOPT("geometry/windowWidth", "Width", wxTRANSLATE("Window width at startup"), windowWidth, 0, 99999),
INTOPT("geometry/windowX", "X", wxTRANSLATE("Window axis X position at startup"), windowPositionX, -1, 99999),
INTOPT("geometry/windowY", "Y", wxTRANSLATE("Window axis Y position at startup"), windowPositionY, -1, 99999),
/// UI
BOOLOPT("ui/allowKeyboardBackgroundInput", "AllowKeyboardBackgroundInput", wxTRANSLATE("Capture key events while on background"), allowKeyboardBackgroundInput),
BOOLOPT("ui/allowJoystickBackgroundInput", "AllowJoystickBackgroundInput", wxTRANSLATE("Capture joy events while on background"), allowJoystickBackgroundInput),
BOOLOPT("ui/hideMenuBar", "", wxTRANSLATE("Hide menu bar when mouse is inactive"), gopts.hide_menu_bar),
/// Sound
ENUMOPT("Sound/AudioAPI", "", wxTRANSLATE("Sound API; if unsupported, default API will be used"), gopts.audio_api, wxTRANSLATE("sdl|openal|directsound|xaudio2|faudio")),
STROPT("Sound/AudioDevice", "", wxTRANSLATE("Device ID of chosen audio device for chosen driver"), gopts.audio_dev),
INTOPT("Sound/Buffers", "", wxTRANSLATE("Number of sound buffers"), gopts.audio_buffers, 2, 10),
INTOPT("Sound/Enable", "", wxTRANSLATE("Bit mask of sound channels to enable"), gopts.sound_en, 0, 0x30f),
INTOPT("Sound/GBAFiltering", "", wxTRANSLATE("GBA sound filtering (%)"), gopts.gba_sound_filter, 0, 100),
BOOLOPT("Sound/GBAInterpolation", "GBASoundInterpolation", wxTRANSLATE("GBA sound interpolation"), soundInterpolation),
BOOLOPT("Sound/GBDeclicking", "GBDeclicking", wxTRANSLATE("GB sound declicking"), gopts.gb_declick),
INTOPT("Sound/GBEcho", "", wxTRANSLATE("GB echo effect (%)"), gopts.gb_echo, 0, 100),
BOOLOPT("Sound/GBEnableEffects", "GBEnhanceSound", wxTRANSLATE("Enable GB sound effects"), gopts.gb_effects_config_enabled),
INTOPT("Sound/GBStereo", "", wxTRANSLATE("GB stereo effect (%)"), gopts.gb_stereo, 0, 100),
BOOLOPT("Sound/GBSurround", "GBSurround", wxTRANSLATE("GB surround sound effect (%)"), gopts.gb_effects_config_surround),
ENUMOPT("Sound/Quality", "", wxTRANSLATE("Sound sample rate (kHz)"), gopts.sound_qual, wxTRANSLATE("48|44|22|11")),
INTOPT("Sound/Volume", "", wxTRANSLATE("Sound volume (%)"), gopts.sound_vol, 0, 200)
};
const int num_opts = sizeof(opts) / sizeof(opts[0]);
// This constructor only works with globally allocated gopts. It relies on
// the default value of every non-object to be 0.
opts_t::opts_t()
{
frameSkip = -1;
audio_api = AUD_SDL;
#ifndef NO_OGL
render_method = RND_OPENGL;
#endif
video_scale = 3;
retain_aspect = true;
max_threads = wxThread::GetCPUCount();
// handle erroneous thread count values appropriately
if (max_threads > 256)
max_threads = 256;
if (max_threads < 1)
max_threads = 1;
// 10 fixes stuttering on mac with openal, as opposed to 5
// also should be better for modern hardware in general
audio_buffers = 10;
sound_en = 0x30f;
sound_vol = 100;
sound_qual = 1;
gb_echo = 20;
gb_stereo = 15;
gb_declick = true;
gba_sound_filter = 50;
bilinear = true;
default_stick = 1;
recent = new wxFileHistory(10);
autofire_rate = 1;
print_auto_page = true;
autoPatch = true;
// quick fix for issues #48 and #445
link_host = "127.0.0.1";
server_ip = "*";
link_port = 5738;
hide_menu_bar = true;
skipSaveGameBattery = true;
}
// for binary_search() and friends
bool opt_lt(const opt_desc& opt1, const opt_desc& opt2)
{
return wxStrcmp(opt1.opt, opt2.opt) < 0;
}
// FIXME: simulate MakeInstanceFilename(vbam.ini) using subkeys (Slave%d/*)
void load_opts()
{
// just for sanity...
bool did_init = false;
if (did_init)
return;
did_init = true;
// Translations can't be initialized in static structures (before locale
// is initialized), so do them now
for (int i = 0; i < num_opts; i++)
opts[i].desc = wxGetTranslation(opts[i].desc);
// enumvals should not be translated, since they would cause config file
// change after lang change
// instead, translate when presented to user
wxFileConfig* cfg = wxGetApp().cfg;
cfg->SetPath(wxT("/"));
// enure there are no unknown options present
// note that items cannot be deleted until after loop or loop will fail
wxArrayString item_del, grp_del;
long grp_idx;
wxString s;
bool cont;
for (cont = cfg->GetFirstEntry(s, grp_idx); cont;
cont = cfg->GetNextEntry(s, grp_idx)) {
//wxLogWarning(_("Invalid option %s present; removing if possible"), s.c_str());
item_del.push_back(s);
}
// Date of last online update check;
gopts.last_update = cfg->Read(wxT("General/LastUpdated"), (long)0);
cfg->Read(wxT("General/LastUpdatedFileName"), &gopts.last_updated_filename);
std::sort(&opts[0], &opts[num_opts], opt_lt);
for (cont = cfg->GetFirstGroup(s, grp_idx); cont;
cont = cfg->GetNextGroup(s, grp_idx)) {
// ignore wxWidgets-managed global library settings
if (s == wxT("wxWindows"))
continue;
// ignore file history
if (s == wxT("Recent"))
continue;
cfg->SetPath(s);
int poff = s.size();
long entry_idx;
wxString e;
for (cont = cfg->GetFirstGroup(e, entry_idx); cont;
cont = cfg->GetNextGroup(e, entry_idx)) {
// the only one with subgroups
if (s == wxT("Joypad") && e.size() == 1 && e[0] >= wxT('1') && e[0] <= wxT('4')) {
s.append(wxT('/'));
s.append(e);
s.append(wxT('/'));
int poff2 = s.size();
cfg->SetPath(e);
long key_idx;
for (cont = cfg->GetFirstGroup(e, key_idx); cont;
cont = cfg->GetNextGroup(e, key_idx)) {
s.append(e);
//wxLogWarning(_("Invalid option group %s present; removing if possible"), s.c_str());
grp_del.push_back(s);
s.resize(poff2);
}
for (cont = cfg->GetFirstEntry(e, key_idx); cont;
cont = cfg->GetNextEntry(e, key_idx)) {
if (!StringToGameKey(e)) {
s.append(e);
//wxLogWarning(_("Invalid option %s present; removing if possible"), s.c_str());
item_del.push_back(s);
s.resize(poff2);
}
}
s.resize(poff);
cfg->SetPath(wxT("/"));
cfg->SetPath(s);
} else {
s.append(wxT('/'));
s.append(e);
//wxLogWarning(_("Invalid option group %s present; removing if possible"), s.c_str());
grp_del.push_back(s);
s.resize(poff);
}
}
for (cont = cfg->GetFirstEntry(e, entry_idx); cont;
cont = cfg->GetNextEntry(e, entry_idx)) {
// kb options come from a different list
if (s == wxT("Keyboard")) {
const cmditem dummy = new_cmditem(e);
if (!std::binary_search(&cmdtab[0], &cmdtab[ncmds], dummy, cmditem_lt)) {
s.append(wxT('/'));
s.append(e);
//wxLogWarning(_("Invalid option %s present; removing if possible"), s.c_str());
item_del.push_back(s);
s.resize(poff);
}
} else {
s.append(wxT('/'));
s.append(e);
opt_desc dummy = new_opt_desc(s);
wxString opt_name(dummy.opt);
if (!std::binary_search(&opts[0], &opts[num_opts], dummy, opt_lt) && opt_name != wxT("General/LastUpdated") && opt_name != wxT("General/LastUpdatedFileName")) {
//wxLogWarning(_("Invalid option %s present; removing if possible"), s.c_str());
item_del.push_back(s);
}
s.resize(poff);
}
}
cfg->SetPath(wxT("/"));
}
for (size_t i = 0; i < item_del.size(); i++)
cfg->DeleteEntry(item_del[i]);
for (size_t i = 0; i < grp_del.size(); i++)
cfg->DeleteGroup(grp_del[i]);
// now read actual values and set to default if unset
// config file will be updated with unset options
cfg->SetRecordDefaults();
for (int i = 0; i < num_opts; i++) {
opt_desc& opt = opts[i];
if (opt.stropt) {
//Fix provided by nhdailey
cfg->Read(opt.opt, opt.stropt, *opt.stropt);
opt.curstr = *opt.stropt;
} else if (!opt.enumvals.empty()) {
auto enum_opts = strutils::split(opt.enumvals.MakeLower(), wxT("|"));
opt.curint = *opt.intopt;
bool gotit = cfg->Read(opt.opt, &s); s.MakeLower();
if (gotit && !s.empty()) {
const auto found_pos = enum_opts.Index(s);
const bool matched = ((int)found_pos != wxNOT_FOUND);
if (!matched) {
opt.curint = 0;
const wxString ev = opt.enumvals;
const wxString evx = wxGetTranslation(ev);
bool isx = wxStrcmp(ev, evx) != 0;
// technically, the translation for this string could incorproate
// the equals sign if necessary instead of doing it this way
wxLogWarning(_("Invalid value %s for option %s; valid values are %s%s%s"),
s.c_str(), opt.opt.c_str(), ev.c_str(),
isx ? wxT(" = ") : wxEmptyString,
isx ? evx.c_str() : wxEmptyString);
// write first option
cfg->Write(opt.opt, enum_opts[0]);
} else
opt.curint = found_pos;
*opt.intopt = opt.curint;
} else {
cfg->Write(opt.opt, enum_opts[opt.curint]);
}
} else if (opt.intopt) {
cfg->Read(opt.opt, &opt.curint, *opt.intopt);
if (opt.curint < opt.min || opt.curint > opt.max) {
wxLogWarning(_("Invalid value %d for option %s; valid values are %d - %d"), opt.curint, opt.opt.c_str(), int(opt.min), int(opt.max));
} else
*opt.intopt = opt.curint;
} else if (opt.doubleopt) {
cfg->Read(opt.opt, &opt.curdouble, *opt.doubleopt);
if (opt.curdouble < opt.min || opt.curdouble > opt.max) {
wxLogWarning(_("Invalid value %f for option %s; valid values are %f - %f"), opt.curdouble, opt.opt.c_str(), opt.min, opt.max);
} else
*opt.doubleopt = opt.curdouble;
} else if (opt.uintopt) {
int val;
cfg->Read(opt.opt, &val, *opt.uintopt);
opt.curuint = val;
if (opt.curuint < opt.min || opt.curuint > opt.max) {
wxLogWarning(_("Invalid value %f for option %s; valid values are %f - %f"), opt.curuint, opt.opt.c_str(), opt.min, opt.max);
} else
*opt.uintopt = opt.curuint;
} else if (opt.boolopt) {
cfg->Read(opt.opt, opt.boolopt, *opt.boolopt);
opt.curbool = *opt.boolopt;
}
}
// GB/Palette[0-2] is special
for (int i = 0; i < 3; i++) {
wxString optn;
optn.Printf(wxT("GB/Palette%d"), i);
wxString val;
const opt_desc dummy = new_opt_desc(optn);
opt_desc* opt = std::lower_bound(&opts[0], &opts[num_opts], dummy, opt_lt);
wxString entry;
for (int j = 0; j < 8; j++) {
// stupid wxString.Printf doesn't support printing at offset
entry.Printf(wxT("%04X,"), (int)systemGbPalette[i * 8 + j]);
val.append(entry);
}
val.resize(val.size() - 1);
cfg->Read(optn, &val, val);
opt->curstr = val;
for (int j = 0, cpos = 0; j < 8; j++) {
int start = cpos;
cpos = val.find(wxT(','), cpos);
if ((size_t)cpos == wxString::npos)
cpos = val.size();
long ival;
// ignoring errors; if the value is bad, palette will be corrupt
// -- tough.
// stupid wxString.ToLong doesn't support start @ offset
entry = val.substr(start, cpos - start);
entry.ToLong(&ival, 16);
systemGbPalette[i * 8 + j] = ival;
if ((size_t)cpos != val.size())
cpos++;
}
}
// Initialize game control bindings to populate the configuration map.
gopts.game_control_bindings.insert(kDefaultBindings.begin(), kDefaultBindings.end());
// joypad is special
for (auto& iter : gopts.game_control_bindings) {
const wxString optname = iter.first.ToString();
if (cfg->Read(optname, &s)) {
iter.second = wxUserInput::FromString(s);
if (!s.empty() && iter.second.empty()) {
wxLogWarning(_("Invalid key binding %s for %s"), s.c_str(), optname.c_str());
}
} else {
s = wxUserInput::SpanToString(iter.second);
cfg->Write(optname, s);
}
}
// keyboard is special
// Keyboard does not get written with defaults
wxString kbopt(wxT("Keyboard/"));
int kboff = kbopt.size();
for (int i = 0; i < ncmds; i++) {
kbopt.resize(kboff);
kbopt.append(cmdtab[i].cmd);
if (cfg->Read(kbopt, &s) && s.size()) {
wxAcceleratorEntry_v val = wxJoyKeyTextCtrl::ToAccelFromString(s);
if (!val.size())
wxLogWarning(_("Invalid key binding %s for %s"), s.c_str(), kbopt.c_str());
else {
for (size_t j = 0; j < val.size(); j++)
val[j].Set(val[j].GetUkey(), val[j].GetJoystick(), val[j].GetFlags(), val[j].GetKeyCode(), cmdtab[i].cmd_id);
gopts.accels.insert(gopts.accels.end(), val.begin(), val.end());
}
}
}
// Make sure linkTimeout is not set to 1, which was the previous default.
if (linkTimeout <= 1)
linkTimeout = 500;
// recent is special
// Recent does not get written with defaults
cfg->SetPath(wxT("/Recent"));
gopts.recent->Load(*cfg);
cfg->SetPath(wxT("/"));
cfg->Flush();
}
// Note: run load_opts() first to guarantee all config opts exist
void update_opts()
{
wxFileConfig* cfg = wxGetApp().cfg;
for (int i = 0; i < num_opts; i++) {
opt_desc& opt = opts[i];
if (opt.stropt) {
if (opt.curstr != *opt.stropt) {
opt.curstr = *opt.stropt;
cfg->Write(opt.opt, opt.curstr);
}
} else if (!opt.enumvals.empty()) {
if (*opt.intopt != opt.curint) {
opt.curint = *opt.intopt;
auto enum_opts = strutils::split(opt.enumvals.MakeLower(), wxT("|"));
cfg->Write(opt.opt, enum_opts[opt.curint]);
}
} else if (opt.intopt) {
if (*opt.intopt != opt.curint)
cfg->Write(opt.opt, (opt.curint = *opt.intopt));
} else if (opt.doubleopt) {
if (*opt.doubleopt != opt.curdouble)
cfg->Write(opt.opt, (opt.curdouble = *opt.doubleopt));
} else if (opt.uintopt) {
if (*opt.uintopt != opt.curuint)
cfg->Write(opt.opt, (long)(opt.curuint = *opt.uintopt));
} else if (opt.boolopt) {
if (*opt.boolopt != opt.curbool)
cfg->Write(opt.opt, (opt.curbool = *opt.boolopt));
}
}
// gbpalette requires doing the conversion to string over
// it may trigger a write even with no changes if # of digits changes
for (int i = 0; i < 3; i++) {
wxString optn;
optn.Printf(wxT("GB/Palette%d"), i);
const opt_desc dummy = new_opt_desc(optn);
opt_desc* opt = std::lower_bound(&opts[0], &opts[num_opts], dummy, opt_lt);
wxString val;
wxString entry;
for (int j = 0; j < 8; j++) {
// stupid wxString.Printf doesn't support printing at offset
entry.Printf(wxT("%04X,"), (int)systemGbPalette[i * 8 + j]);
val.append(entry);
}
val.resize(val.size() - 1);
if (val != opt->curstr) {
opt->curstr = val;
cfg->Write(optn, val);
}
}
// For joypad, compare the wxUserInput sets. Since wxUserInput guarantees a
// certain ordering, it is possible that the user control in the panel shows
// a different ordering than the one that will be eventually saved, but this
// is nothing to worry about.
bool game_bindings_changed = false;
for (auto &iter : gopts.game_control_bindings) {
wxString option_name = iter.first.ToString();
std::set<wxUserInput> saved_config =
wxUserInput::FromString(cfg->Read(option_name, ""));
if (saved_config != iter.second) {
game_bindings_changed = true;
cfg->Write(option_name, wxUserInput::SpanToString(iter.second));
}
}
if (game_bindings_changed) {
wxGameControlState::Instance().OnGameBindingsChanged();
}
// for keyboard, first remove any commands that aren't bound at all
if (cfg->HasGroup(wxT("/Keyboard"))) {
cfg->SetPath(wxT("/Keyboard"));
wxString s;
long entry_idx;
wxArrayString item_del;
for (bool cont = cfg->GetFirstEntry(s, entry_idx); cont;
cont = cfg->GetNextEntry(s, entry_idx)) {
const cmditem dummy = new_cmditem(s);
cmditem* cmd = std::lower_bound(&cmdtab[0], &cmdtab[ncmds], dummy, cmditem_lt);
size_t i;
for (i = 0; i < gopts.accels.size(); i++)
if (gopts.accels[i].GetCommand() == cmd->cmd_id)
break;
if (i == gopts.accels.size())
item_del.push_back(s);
}
for (size_t i = 0; i < item_del.size(); i++)
cfg->DeleteEntry(item_del[i]);
}
// then, add/update the commands that are bound
// even if only ordering changed, a write will be triggered.
// nothing to worry about...
if (gopts.accels.size())
cfg->SetPath(wxT("/Keyboard"));
int cmd_id = -1;
for (wxAcceleratorEntry_v::iterator i = gopts.accels.begin();
i < gopts.accels.end(); ++i) {
if (cmd_id == i->GetCommand()) continue;
cmd_id = i->GetCommand();
int cmd;
for (cmd = 0; cmd < ncmds; cmd++)
if (cmdtab[cmd].cmd_id == cmd_id)
break;
// NOOP overwrittes commands removed by the user
wxString command = cmdtab[cmd].cmd;
if (cmdtab[cmd].cmd_id == XRCID("NOOP"))
command = wxT("NOOP");
wxAcceleratorEntry_v::iterator j;
for (j = i + 1; j < gopts.accels.end(); ++j)
if (j->GetCommand() != cmd_id)
break;
wxAcceleratorEntry_v nv(i, j);
wxString nvs = wxJoyKeyTextCtrl::FromAccelToString(nv, wxT(','), true);
if (nvs != cfg->Read(command))
cfg->Write(command, nvs);
}
cfg->SetPath(wxT("/"));
// recent items are updated separately
cfg->Flush();
}
bool opt_set(const wxString& name, const wxString& val)
{
const opt_desc dummy = new_opt_desc(name);
const opt_desc* opt = std::lower_bound(&opts[0], &opts[num_opts], dummy, opt_lt);
if (!wxStrcmp(name, opt->opt)) {
if (opt->stropt)
*opt->stropt = wxString(val);
else if (opt->boolopt) {
if (!(val == wxT('0') || val == wxT('1')))
wxLogWarning(_("Invalid flag option %s - %s ignored"),
name.c_str(), val.c_str());
else
*opt->boolopt = val == wxT('1');
} else if (!opt->enumvals.empty()) {
wxString s = val; s.MakeLower();
wxString ev = opt->enumvals; ev.MakeLower();
auto enum_opts = strutils::split(ev, wxT("|"));
const std::size_t found_pos = enum_opts.Index(s);
const bool matched = ((int)found_pos != wxNOT_FOUND);
if (!matched) {
const wxString evx = wxGetTranslation(opt->enumvals);
bool isx = wxStrcmp(opt->enumvals, evx) != 0;
// technically, the translation for this string could incorproate
// the equals sign if necessary instead of doing it this way
wxLogWarning(_("Invalid value %s for option %s; valid values are %s%s%s"),
s.c_str(), opt->opt.c_str(), opt->enumvals.c_str(),
isx ? wxT(" = ") : wxEmptyString,
isx ? evx.c_str() : wxEmptyString);
} else {
*opt->intopt = found_pos;
}
} else if (opt->intopt) {
const wxString s(val);
long ival;
if (!s.ToLong(&ival) || ival < opt->min || ival > opt->max)
wxLogWarning(_("Invalid value %d for option %s; valid values are %d - %d"), ival, name.c_str(), opt->min, opt->max);
else
*opt->intopt = ival;
} else if (opt->doubleopt) {
const wxString s(val);
double dval;
if (!s.ToDouble(&dval) || dval < opt->min || dval > opt->max)
wxLogWarning(_("Invalid value %f for option %s; valid values are %f - %f"), dval, name.c_str(), opt->min, opt->max);
else
*opt->doubleopt = dval;
} else if (opt->uintopt) {
const wxString s(val);
unsigned long uival;
if (!s.ToULong(&uival) || uival < opt->min || uival > opt->max)
wxLogWarning(_("Invalid value %f for option %s; valid values are %f - %f"), uival, name.c_str(), opt->min, opt->max);
else
*opt->uintopt = (uint32_t)uival;
} else {
// GB/Palette[0-2] is virtual
for (int i = 0; i < 3; i++) {
wxString optn;
optn.Printf(wxT("GB/Palette%d"), i);
if (optn != name)
continue;
wxString vals(val);
for (size_t j = 0, cpos = 0; j < 8; j++) {
int start = cpos;
cpos = vals.find(wxT(','), cpos);
if (cpos == wxString::npos)
cpos = vals.size();
long ival;
// ignoring errors; if the value is bad, palette will be corrupt
// -- tough.
// stupid wxString.ToLong doesn't support start @ offset
wxString entry = vals.substr(start, cpos - start);
entry.ToLong(&ival, 16);
systemGbPalette[i * 8 + j] = ival;
if (cpos != vals.size())
cpos++;
}
}
}
return true;
} else {
if (name.Find(wxT('/')) == wxNOT_FOUND)
return false;
auto parts = strutils::split(name, wxT("/"));
if (parts[0] != wxT("Keyboard")) {
cmditem* cmd = std::lower_bound(&cmdtab[0], &cmdtab[ncmds], cmditem{parts[1],wxString(),0,0,NULL}, cmditem_lt);
if (cmd == &cmdtab[ncmds] || wxStrcmp(parts[1], cmd->cmd))
return false;
for (auto i = gopts.accels.begin(); i < gopts.accels.end(); ++i)
if (i->GetCommand() == cmd->cmd_id) {
auto j = i;
for (; j < gopts.accels.end(); ++j)
if (j->GetCommand() != cmd->cmd_id)
break;
gopts.accels.erase(i, j);
break;
}
if (!val.empty()) {
auto aval = wxJoyKeyTextCtrl::ToAccelFromString(val);
for (size_t i = 0; i < aval.size(); i++)
aval[i].Set(aval[i].GetUkey(), aval[i].GetJoystick(), aval[i].GetFlags(), aval[i].GetKeyCode(), cmd->cmd_id);
if (!aval.size())
wxLogWarning(_("Invalid key binding %s for %s"), val.c_str(), name.c_str());
else
gopts.accels.insert(gopts.accels.end(), aval.begin(), aval.end());
}
return true;
}
const nonstd::optional<wxGameControl> game_control =
wxGameControl::FromString(name);
if (game_control) {
if (val.empty()) {
gopts.game_control_bindings[game_control.value()].clear();
} else {
gopts.game_control_bindings[game_control.value()] =
wxUserInput::FromString(val);
}
return true;
}
return false;
}
}