//============================================================================ // // SSSS tt lll lll // SS SS tt ll ll // SS tttttt eeee ll ll aaaa // SSSS tt ee ee ll ll aa // SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" // SS SS tt ee ll ll aa aa // SSSS ttt eeeee llll llll aaaaa // // Copyright (c) 1995-2018 by Bradford W. Mott, Stephen Anthony // and the Stella Team // // See the file "License.txt" for information on usage and redistribution of // this file, and for a DISCLAIMER OF ALL WARRANTIES. //============================================================================ #include "bspf.hxx" #include "OSystem.hxx" #include "Joystick.hxx" #include "Paddles.hxx" #include "PointingDevice.hxx" #include "SaveKey.hxx" #include "AtariVox.hxx" #include "Settings.hxx" #include "EditTextWidget.hxx" #include "PopUpWidget.hxx" #include "RadioButtonWidget.hxx" #include "ColorWidget.hxx" #include "TabWidget.hxx" #include "Widget.hxx" #include "Font.hxx" #ifdef DEBUGGER_SUPPORT #include "DebuggerDialog.hxx" #endif #include "Console.hxx" #include "TIA.hxx" #include "OSystem.hxx" #include "StateManager.hxx" #include "RewindManager.hxx" #include "M6502.hxx" #include "DeveloperDialog.hxx" // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - DeveloperDialog::DeveloperDialog(OSystem& osystem, DialogContainer& parent, const GUI::Font& font, int max_w, int max_h) : Dialog(osystem, parent, font, "Developer settings") { const int VGAP = 4; const int lineHeight = font.getLineHeight(), fontWidth = font.getMaxCharWidth(), buttonHeight = font.getLineHeight() + 4; int xpos, ypos; // Set real dimensions _w = std::min(53 * fontWidth + 10, max_w); _h = std::min(15 * (lineHeight + VGAP) + 14 + _th, max_h); // The tab widget xpos = 2; ypos = 4; myTab = new TabWidget(this, font, xpos, ypos + _th, _w - 2 * xpos, _h - _th - buttonHeight - 16 - ypos); addTabWidget(myTab); addEmulationTab(font); addVideoTab(font); addTimeMachineTab(font); addDebuggerTab(font); WidgetArray wid; addDefaultsOKCancelBGroup(wid, font); addBGroupToFocusList(wid); // Activate the first tab myTab->setActiveTab(0); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void DeveloperDialog::addEmulationTab(const GUI::Font& font) { const int HBORDER = 10; const int INDENT = 16+4; const int VBORDER = 8; const int VGAP = 4; int ypos = VBORDER; int lineHeight = font.getLineHeight(); WidgetArray wid; VariantList items; int tabID = myTab->addTab("Emulation"); // settings set mySettingsGroup0 = new RadioButtonGroup(); RadioButtonWidget* r = new RadioButtonWidget(myTab, font, HBORDER, ypos + 1, "Player settings", mySettingsGroup0, kPlrSettings); wid.push_back(r); ypos += lineHeight + VGAP; r = new RadioButtonWidget(myTab, font, HBORDER, ypos + 1, "Developer settings", mySettingsGroup0, kDevSettings); wid.push_back(r); ypos += lineHeight + VGAP * 1; myFrameStatsWidget = new CheckboxWidget(myTab, font, HBORDER + INDENT * 1, ypos + 1, "Frame statistics"); wid.push_back(myFrameStatsWidget); ypos += lineHeight + VGAP; // 2600/7800 mode items.clear(); VarList::push_back(items, "Atari 2600", "2600"); VarList::push_back(items, "Atari 7800", "7800"); int lwidth = font.getStringWidth("Console "); int pwidth = font.getStringWidth("Atari 2600"); myConsoleWidget = new PopUpWidget(myTab, font, HBORDER + INDENT * 1, ypos, pwidth, lineHeight, items, "Console ", lwidth, kConsole); wid.push_back(myConsoleWidget); ypos += lineHeight + VGAP; // Randomize items myLoadingROMLabel = new StaticTextWidget(myTab, font, HBORDER + INDENT*1, ypos + 1, "When loading a ROM:"); wid.push_back(myLoadingROMLabel); ypos += lineHeight + VGAP; myRandomBankWidget = new CheckboxWidget(myTab, font, HBORDER + INDENT * 2, ypos + 1, "Random startup bank"); wid.push_back(myRandomBankWidget); ypos += lineHeight + VGAP; // Randomize RAM myRandomizeRAMWidget = new CheckboxWidget(myTab, font, HBORDER + INDENT * 2, ypos + 1, "Randomize zero-page and extended RAM", kRandRAMID); wid.push_back(myRandomizeRAMWidget); ypos += lineHeight + VGAP; // Randomize CPU lwidth = font.getStringWidth("Randomize CPU "); myRandomizeCPULabel = new StaticTextWidget(myTab, font, HBORDER + INDENT * 2, ypos + 1, "Randomize CPU "); wid.push_back(myRandomizeCPULabel); int xpos = myRandomizeCPULabel->getRight() + 10; const char* const cpuregs[] = { "SP", "A", "X", "Y", "PS" }; for(int i = 0; i < 5; ++i) { myRandomizeCPUWidget[i] = new CheckboxWidget(myTab, font, xpos, ypos + 1, cpuregs[i], kRandCPUID); wid.push_back(myRandomizeCPUWidget[i]); xpos += CheckboxWidget::boxSize() + font.getStringWidth("XX") + 20; } ypos += lineHeight + VGAP; // How to handle undriven TIA pins myUndrivenPinsWidget = new CheckboxWidget(myTab, font, HBORDER + INDENT * 1, ypos + 1, "Drive unused TIA pins randomly on a read/peek"); wid.push_back(myUndrivenPinsWidget); ypos += lineHeight + VGAP; // Thumb ARM emulation exception myThumbExceptionWidget = new CheckboxWidget(myTab, font, HBORDER + INDENT * 1, ypos + 1, "Fatal ARM emulation error throws exception"); wid.push_back(myThumbExceptionWidget); ypos += lineHeight + VGAP; // AtariVox/SaveKey EEPROM access myEEPROMAccessWidget = new CheckboxWidget(myTab, font, HBORDER + INDENT * 1, ypos + 1, "Display AtariVox/SaveKey EEPROM R/W access"); wid.push_back(myEEPROMAccessWidget); // Add items for tab 0 addToFocusList(wid, myTab, tabID); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void DeveloperDialog::addVideoTab(const GUI::Font& font) { const int HBORDER = 10; const int INDENT = 16 + 4; const int VBORDER = 8; const int VGAP = 4; int ypos = VBORDER; int lineHeight = font.getLineHeight(); int fontWidth = font.getMaxCharWidth(), fontHeight = font.getFontHeight(); int lwidth = font.getStringWidth("Intensity "); int pwidth = font.getMaxCharWidth() * 6; WidgetArray wid; VariantList items; int tabID = myTab->addTab("Video"); wid.clear(); ypos = VBORDER; // settings set mySettingsGroup1 = new RadioButtonGroup(); RadioButtonWidget* r = new RadioButtonWidget(myTab, font, HBORDER, ypos + 1, "Player settings", mySettingsGroup1, kPlrSettings); wid.push_back(r); ypos += lineHeight + VGAP; r = new RadioButtonWidget(myTab, font, HBORDER, ypos + 1, "Developer settings", mySettingsGroup1, kDevSettings); wid.push_back(r); ypos += lineHeight + VGAP * 1; // TV jitter effect myTVJitterWidget = new CheckboxWidget(myTab, font, HBORDER + INDENT * 1, ypos + 1, "Jitter/roll effect", kTVJitter); wid.push_back(myTVJitterWidget); myTVJitterRecWidget = new SliderWidget(myTab, font, myTVJitterWidget->getRight() + fontWidth * 3, ypos - 1, "Recovery ", 0, kTVJitterChanged); myTVJitterRecWidget->setMinValue(1); myTVJitterRecWidget->setMaxValue(20); myTVJitterRecWidget->setTickmarkInterval(5); wid.push_back(myTVJitterRecWidget); myTVJitterRecLabelWidget = new StaticTextWidget(myTab, font, myTVJitterRecWidget->getRight() + 4, myTVJitterRecWidget->getTop() + 2, 5 * fontWidth, fontHeight, ""); ypos += lineHeight + VGAP; myColorLossWidget = new CheckboxWidget(myTab, font, HBORDER + INDENT * 1, ypos + 1, "PAL color-loss"); wid.push_back(myColorLossWidget); ypos += lineHeight + VGAP; // debug colors myDebugColorsWidget = new CheckboxWidget(myTab, font, HBORDER + INDENT * 1, ypos + 1, "Debug colors (*)"); wid.push_back(myDebugColorsWidget); ypos += lineHeight + VGAP + 2; items.clear(); VarList::push_back(items, "Red", "r"); VarList::push_back(items, "Orange", "o"); VarList::push_back(items, "Yellow", "y"); VarList::push_back(items, "Green", "g"); VarList::push_back(items, "Purple", "p"); VarList::push_back(items, "Blue", "b"); static constexpr int dbg_cmds[DEBUG_COLORS] = { kP0ColourChangedCmd, kM0ColourChangedCmd, kP1ColourChangedCmd, kM1ColourChangedCmd, kPFColourChangedCmd, kBLColourChangedCmd }; auto createDebugColourWidgets = [&](int idx, const string& desc) { int x = HBORDER + INDENT * 1; myDbgColour[idx] = new PopUpWidget(myTab, font, x, ypos - 1, pwidth, lineHeight, items, desc, lwidth, dbg_cmds[idx]); wid.push_back(myDbgColour[idx]); x += myDbgColour[idx]->getWidth() + 10; myDbgColourSwatch[idx] = new ColorWidget(myTab, font, x, ypos - 1, uInt32(2 * lineHeight), lineHeight); ypos += lineHeight + VGAP * 1; }; createDebugColourWidgets(0, "Player 0 "); createDebugColourWidgets(1, "Missile 0 "); createDebugColourWidgets(2, "Player 1 "); createDebugColourWidgets(3, "Missile 1 "); createDebugColourWidgets(4, "Playfield "); createDebugColourWidgets(5, "Ball "); // Add message concerning usage const GUI::Font& infofont = instance().frameBuffer().infoFont(); ypos = myTab->getHeight() - 5 - fontHeight - infofont.getFontHeight() - 10; new StaticTextWidget(myTab, infofont, HBORDER, ypos, "(*) colors identical for player and developer settings"); // Add items for tab 2 addToFocusList(wid, myTab, tabID); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void DeveloperDialog::addTimeMachineTab(const GUI::Font& font) { const string INTERVALS[NUM_INTERVALS] = { " 1 frame", " 3 frames", "10 frames", "30 frames", " 1 second", " 3 seconds", "10 seconds" }; const string INT_SETTINGS[NUM_INTERVALS] = { "1f", "3f", "10f", "30f", "1s", "3s", "10s" }; const string HORIZONS[NUM_HORIZONS] = { " 3 seconds", "10 seconds", "30 seconds", " 1 minute", " 3 minutes", "10 minutes", "30 minutes", "60 minutes" }; const string HOR_SETTINGS[NUM_HORIZONS] = { "3s", "10s", "30s", "1m", "3m", "10m", "30m", "60m" }; const int HBORDER = 10; const int INDENT = 16+4; const int VBORDER = 8; const int VGAP = 4; int ypos = VBORDER; int lineHeight = font.getLineHeight(), fontHeight = font.getFontHeight(), fontWidth = font.getMaxCharWidth(), lwidth = fontWidth * 11; WidgetArray wid; VariantList items; int tabID = myTab->addTab("Time Machine"); // settings set mySettingsGroup2 = new RadioButtonGroup(); RadioButtonWidget* r = new RadioButtonWidget(myTab, font, HBORDER, ypos + 1, "Player settings", mySettingsGroup2, kPlrSettings); wid.push_back(r); ypos += lineHeight + VGAP; r = new RadioButtonWidget(myTab, font, HBORDER, ypos + 1, "Developer settings", mySettingsGroup2, kDevSettings); wid.push_back(r); ypos += lineHeight + VGAP * 1; myTimeMachineWidget = new CheckboxWidget(myTab, font, HBORDER + INDENT, ypos + 1, "Time Machine", kTimeMachine); wid.push_back(myTimeMachineWidget); ypos += lineHeight + VGAP; int swidth = fontWidth * 12 + 5; // width of PopUpWidgets below myStateSizeWidget = new SliderWidget(myTab, font, HBORDER + INDENT * 2, ypos - 1, swidth, lineHeight, "Buffer size (*) ", 0, kSizeChanged, lwidth, " states"); myStateSizeWidget->setMinValue(20); myStateSizeWidget->setMaxValue(1000); myStateSizeWidget->setStepValue(20); wid.push_back(myStateSizeWidget); ypos += lineHeight + VGAP; myUncompressedWidget = new SliderWidget(myTab, font, HBORDER + INDENT * 2, ypos - 1, swidth, lineHeight, "Uncompressed size ", 0, kUncompressedChanged, lwidth, " states"); myUncompressedWidget->setMinValue(0); myUncompressedWidget->setMaxValue(1000); myUncompressedWidget->setStepValue(20); wid.push_back(myUncompressedWidget); ypos += lineHeight + VGAP; items.clear(); for(int i = 0; i < NUM_INTERVALS; ++i) VarList::push_back(items, INTERVALS[i], INT_SETTINGS[i]); int pwidth = font.getStringWidth("10 seconds"); myStateIntervalWidget = new PopUpWidget(myTab, font, HBORDER + INDENT * 2, ypos, pwidth, lineHeight, items, "Interval ", 0, kIntervalChanged); wid.push_back(myStateIntervalWidget); ypos += lineHeight + VGAP; items.clear(); for(int i = 0; i < NUM_HORIZONS; ++i) VarList::push_back(items, HORIZONS[i], HOR_SETTINGS[i]); myStateHorizonWidget = new PopUpWidget(myTab, font, HBORDER + INDENT * 2, ypos, pwidth, lineHeight, items, "Horizon ~ ", 0, kHorizonChanged); wid.push_back(myStateHorizonWidget); // Add message concerning usage const GUI::Font& infofont = instance().frameBuffer().infoFont(); ypos = myTab->getHeight() - 5 - fontHeight - infofont.getFontHeight() - 10; new StaticTextWidget(myTab, infofont, HBORDER, ypos, "(*) Any size change clears the buffer"); addToFocusList(wid, myTab, tabID); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void DeveloperDialog::addDebuggerTab(const GUI::Font& font) { int tabID = myTab->addTab("Debugger"); WidgetArray wid; #ifdef DEBUGGER_SUPPORT const int HBORDER = 10; const int VBORDER = 8; const int VGAP = 4; VariantList items; int fontWidth = font.getMaxCharWidth(), fontHeight = font.getFontHeight(), lineHeight = font.getLineHeight(); int xpos, ypos, pwidth; const GUI::Size& ds = instance().frameBuffer().desktopSize(); xpos = HBORDER; ypos = VBORDER; // font size items.clear(); VarList::push_back(items, "Small", "small"); VarList::push_back(items, "Medium", "medium"); VarList::push_back(items, "Large", "large"); pwidth = font.getStringWidth("Medium"); myDebuggerFontSize = new PopUpWidget(myTab, font, HBORDER, ypos + 1, pwidth, lineHeight, items, "Font size (*) ", 0, kDFontSizeChanged); wid.push_back(myDebuggerFontSize); ypos += lineHeight + 4; // Font style (bold label vs. text, etc) items.clear(); VarList::push_back(items, "All Normal font", "0"); VarList::push_back(items, "Bold labels only", "1"); VarList::push_back(items, "Bold non-labels only", "2"); VarList::push_back(items, "All Bold font", "3"); pwidth = font.getStringWidth("Bold non-labels only"); myDebuggerFontStyle = new PopUpWidget(myTab, font, HBORDER, ypos + 1, pwidth, lineHeight, items, "Font style (*) ", 0); wid.push_back(myDebuggerFontStyle); ypos += lineHeight + VGAP * 4; // Debugger width and height myDebuggerWidthSlider = new SliderWidget(myTab, font, xpos, ypos-1, "Debugger width (*) ", 0, 0, 6 * fontWidth, "px"); myDebuggerWidthSlider->setMinValue(DebuggerDialog::kSmallFontMinW); myDebuggerWidthSlider->setMaxValue(ds.w); myDebuggerWidthSlider->setStepValue(10); wid.push_back(myDebuggerWidthSlider); ypos += lineHeight + VGAP; myDebuggerHeightSlider = new SliderWidget(myTab, font, xpos, ypos-1, "Debugger height (*) ", 0, 0, 6 * fontWidth, "px"); myDebuggerHeightSlider->setMinValue(DebuggerDialog::kSmallFontMinH); myDebuggerHeightSlider->setMaxValue(ds.h); myDebuggerHeightSlider->setStepValue(10); wid.push_back(myDebuggerHeightSlider); ypos += lineHeight + VGAP * 4; myGhostReadsTrapWidget = new CheckboxWidget(myTab, font, HBORDER, ypos + 1, "Trap on 'ghost' reads", kGhostReads); wid.push_back(myGhostReadsTrapWidget); // Add message concerning usage const GUI::Font& infofont = instance().frameBuffer().infoFont(); ypos = myTab->getHeight() - 5 - fontHeight - infofont.getFontHeight() - 10; new StaticTextWidget(myTab, infofont, HBORDER, ypos, "(*) Changes require a ROM reload"); // Debugger is only realistically available in windowed modes 800x600 or greater // (and when it's actually been compiled into the app) bool debuggerAvailable = #if defined(DEBUGGER_SUPPORT) && defined(WINDOWED_SUPPORT) (ds.w >= 800 && ds.h >= 600); // TODO - maybe this logic can disappear? #else false; #endif if(!debuggerAvailable) { myDebuggerWidthSlider->clearFlags(WIDGET_ENABLED); myDebuggerHeightSlider->clearFlags(WIDGET_ENABLED); } #else new StaticTextWidget(myTab, font, 0, 20, _w - 20, font.getFontHeight(), "Debugger support not included", TextAlign::Center); #endif addToFocusList(wid, myTab, tabID); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void DeveloperDialog::loadSettings(SettingsSet set) { string prefix = set == SettingsSet::player ? "plr." : "dev."; myFrameStats[set] = instance().settings().getBool(prefix + "stats"); myConsole[set] = instance().settings().getString(prefix + "console") == "7800" ? 1 : 0; // Randomization myRandomBank[set] = instance().settings().getBool(prefix + "bankrandom"); myRandomizeRAM[set] = instance().settings().getBool(prefix + "ramrandom"); myRandomizeCPU[set] = instance().settings().getString(prefix + "cpurandom"); // Undriven TIA pins myUndrivenPins[set] = instance().settings().getBool(prefix + "tiadriven"); // Thumb ARM emulation exception myThumbException[set] = instance().settings().getBool(prefix + "thumb.trapfatal"); // AtariVox/SaveKey EEPROM access myEEPROMAccess[set] = instance().settings().getBool(prefix + "eepromaccess"); // Debug colors myDebugColors[set] = instance().settings().getBool(prefix + "debugcolors"); // PAL color-loss effect myColorLoss[set] = instance().settings().getBool(prefix + "colorloss"); // Jitter myTVJitter[set] = instance().settings().getBool(prefix + "tv.jitter"); myTVJitterRec[set] = instance().settings().getInt(prefix + "tv.jitter_recovery"); // States myTimeMachine[set] = instance().settings().getBool(prefix + "timemachine"); myStateSize[set] = instance().settings().getInt(prefix + "tm.size"); myUncompressed[set] = instance().settings().getInt(prefix + "tm.uncompressed"); myStateInterval[set] = instance().settings().getString(prefix + "tm.interval"); myStateHorizon[set] = instance().settings().getString(prefix + "tm.horizon"); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void DeveloperDialog::saveSettings(SettingsSet set) { string prefix = set == SettingsSet::player ? "plr." : "dev."; instance().settings().setValue(prefix + "stats", myFrameStats[set]); instance().settings().setValue(prefix + "console", myConsole[set] == 1 ? "7800" : "2600"); // Randomization instance().settings().setValue(prefix + "bankrandom", myRandomBank[set]); instance().settings().setValue(prefix + "ramrandom", myRandomizeRAM[set]); instance().settings().setValue(prefix + "cpurandom", myRandomizeCPU[set]); // Undriven TIA pins instance().settings().setValue(prefix + "tiadriven", myUndrivenPins[set]); // Thumb ARM emulation exception instance().settings().setValue(prefix + "thumb.trapfatal", myThumbException[set]); // AtariVox/SaveKey EEPROM access instance().settings().setValue(prefix + "eepromaccess", myEEPROMAccess[set]); // Debug colors instance().settings().setValue(prefix + "debugcolors", myDebugColors[set]); // PAL color loss instance().settings().setValue(prefix + "colorloss", myColorLoss[set]); // Jitter instance().settings().setValue(prefix + "tv.jitter", myTVJitter[set]); instance().settings().setValue(prefix + "tv.jitter_recovery", myTVJitterRec[set]); // States instance().settings().setValue(prefix + "timemachine", myTimeMachine[set]); instance().settings().setValue(prefix + "tm.size", myStateSize[set]); instance().settings().setValue(prefix + "tm.uncompressed", myUncompressed[set]); instance().settings().setValue(prefix + "tm.interval", myStateInterval[set]); instance().settings().setValue(prefix + "tm.horizon", myStateHorizon[set]); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void DeveloperDialog::getWidgetStates(SettingsSet set) { myFrameStats[set] = myFrameStatsWidget->getState(); myConsole[set] = myConsoleWidget->getSelected() == 1; // Randomization myRandomBank[set] = myRandomBankWidget->getState(); myRandomizeRAM[set] = myRandomizeRAMWidget->getState(); string cpurandom; const char* const cpuregs[] = { "S", "A", "X", "Y", "P" }; for(int i = 0; i < 5; ++i) if(myRandomizeCPUWidget[i]->getState()) cpurandom += cpuregs[i]; myRandomizeCPU[set] = cpurandom; // Undriven TIA pins myUndrivenPins[set] = myUndrivenPinsWidget->getState(); // Thumb ARM emulation exception myThumbException[set] = myThumbExceptionWidget->getState(); // AtariVox/SaveKey EEPROM access myEEPROMAccess[set] = myEEPROMAccessWidget->getState(); // Debug colors myDebugColors[set] = myDebugColorsWidget->getState(); // PAL color-loss effect myColorLoss[set] = myColorLossWidget->getState(); // Jitter myTVJitter[set] = myTVJitterWidget->getState(); myTVJitterRec[set] = myTVJitterRecWidget->getValue(); // States myTimeMachine[set] = myTimeMachineWidget->getState(); myStateSize[set] = myStateSizeWidget->getValue(); myUncompressed[set] = myUncompressedWidget->getValue(); myStateInterval[set] = myStateIntervalWidget->getSelected(); myStateInterval[set] = myStateIntervalWidget->getSelectedTag().toString(); myStateHorizon[set] = myStateHorizonWidget->getSelectedTag().toString(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void DeveloperDialog::setWidgetStates(SettingsSet set) { myFrameStatsWidget->setState(myFrameStats[set]); myConsoleWidget->setSelectedIndex(myConsole[set]); // Randomization myRandomBankWidget->setState(myRandomBank[set]); myRandomizeRAMWidget->setState(myRandomizeRAM[set]); const string& cpurandom = myRandomizeCPU[set]; const char* const cpuregs[] = { "S", "A", "X", "Y", "P" }; for(int i = 0; i < 5; ++i) myRandomizeCPUWidget[i]->setState(BSPF::containsIgnoreCase(cpurandom, cpuregs[i])); // Undriven TIA pins myUndrivenPinsWidget->setState(myUndrivenPins[set]); // Thumb ARM emulation exception myThumbExceptionWidget->setState(myThumbException[set]); // AtariVox/SaveKey EEPROM access myEEPROMAccessWidget->setState(myEEPROMAccess[set]); handleConsole(); // Debug colors myDebugColorsWidget->setState(myDebugColors[set]); // PAL color-loss effect myColorLossWidget->setState(myColorLoss[set]); // Jitter myTVJitterWidget->setState(myTVJitter[set]); myTVJitterRecWidget->setValue(myTVJitterRec[set]); handleTVJitterChange(myTVJitterWidget->getState()); handleEnableDebugColors(); // States myTimeMachineWidget->setState(myTimeMachine[set]); myStateSizeWidget->setValue(myStateSize[set]); myUncompressedWidget->setValue(myUncompressed[set]); myStateIntervalWidget->setSelected(myStateInterval[set]); myStateHorizonWidget->setSelected(myStateHorizon[set]); handleTimeMachine(); handleSize(); handleUncompressed(); handleInterval(); handleHorizon(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void DeveloperDialog::loadConfig() { bool devSettings = instance().settings().getBool("dev.settings"); mySettings = devSettings; mySettingsGroup0->setSelected(devSettings ? 1 : 0); mySettingsGroup1->setSelected(devSettings ? 1 : 0); mySettingsGroup2->setSelected(devSettings ? 1 : 0); // load both setting sets... loadSettings(SettingsSet::player); loadSettings(SettingsSet::developer); // ...and select the current one setWidgetStates(SettingsSet(mySettingsGroup0->getSelected())); // Debug colours handleDebugColours(instance().settings().getString("tia.dbgcolors")); #ifdef DEBUGGER_SUPPORT uInt32 w, h; // Debugger size const GUI::Size& ds = instance().settings().getSize("dbg.res"); w = ds.w; h = ds.h; myDebuggerWidthSlider->setValue(w); myDebuggerHeightSlider->setValue(h); // Debugger font size string size = instance().settings().getString("dbg.fontsize"); myDebuggerFontSize->setSelected(size, "medium"); // Debugger font style int style = instance().settings().getInt("dbg.fontstyle"); myDebuggerFontStyle->setSelected(style, "0"); // Ghost reads trap myGhostReadsTrapWidget->setState(instance().settings().getBool("dbg.ghostreadstrap")); handleFontSize(); #endif myTab->loadConfig(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void DeveloperDialog::saveConfig() { instance().settings().setValue("dev.settings", mySettingsGroup0->getSelected() == SettingsSet::developer); // copy current widget status into set... getWidgetStates(SettingsSet(mySettingsGroup0->getSelected())); // ...and save both sets saveSettings(SettingsSet::player); saveSettings(SettingsSet::developer); // activate the current settings instance().frameBuffer().showFrameStats(myFrameStatsWidget->getState()); // jitter if(instance().hasConsole()) { instance().console().tia().toggleJitter(myTVJitterWidget->getState() ? 1 : 0); instance().console().tia().setJitterRecoveryFactor(myTVJitterRecWidget->getValue()); } handleEnableDebugColors(); // PAL color loss if(instance().hasConsole()) instance().console().enableColorLoss(myColorLossWidget->getState()); // Debug colours string dbgcolors; for(int i = 0; i < DEBUG_COLORS; ++i) dbgcolors += myDbgColour[i]->getSelectedTag().toString(); if(instance().hasConsole() && instance().console().tia().setFixedColorPalette(dbgcolors)) instance().settings().setValue("tia.dbgcolors", dbgcolors); // update RewindManager instance().state().rewindManager().setup(); instance().state().setRewindMode(myTimeMachineWidget->getState() ? StateManager::Mode::TimeMachine : StateManager::Mode::Off); #ifdef DEBUGGER_SUPPORT // Debugger font style instance().settings().setValue("dbg.fontstyle", myDebuggerFontStyle->getSelectedTag().toString()); // Debugger size instance().settings().setValue("dbg.res", GUI::Size(myDebuggerWidthSlider->getValue(), myDebuggerHeightSlider->getValue())); // Debugger font size instance().settings().setValue("dbg.fontsize", myDebuggerFontSize->getSelectedTag().toString()); // Ghost reads trap instance().settings().setValue("dbg.ghostreadstrap", myGhostReadsTrapWidget->getState()); if(instance().hasConsole()) instance().console().system().m6502().setGhostReadsTrap(myGhostReadsTrapWidget->getState()); #endif } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void DeveloperDialog::setDefaults() { bool devSettings = mySettingsGroup0->getSelected() == 1; SettingsSet set = SettingsSet(mySettingsGroup0->getSelected()); switch(myTab->getActiveTab()) { case 0: // Emulation myFrameStats[set] = devSettings ? true : false; myConsole[set] = 0; // Randomization myRandomBank[set] = devSettings ? true : false; myRandomizeRAM[set] = devSettings ? true : false; myRandomizeCPU[set] = devSettings ? "SAXYP" : ""; // Undriven TIA pins myUndrivenPins[set] = devSettings ? true : false; // Thumb ARM emulation exception myThumbException[set] = devSettings ? true : false; // AtariVox/SaveKey EEPROM access myEEPROMAccess[set] = devSettings ? true : false; setWidgetStates(set); break; case 1: // Video // Jitter myTVJitter[set] = true; myTVJitterRec[set] = devSettings ? 2 : 10; // PAL color-loss effect myColorLoss[set] = devSettings ? true : false; // Debug colors myDebugColors[set] = false; handleDebugColours("roygpb"); setWidgetStates(set); break; case 2: // States myTimeMachine[set] = devSettings ? true : false; myStateSize[set] = 100; myUncompressed[set] = devSettings ? 60 : 30; myStateInterval[set] = devSettings ? "1f" : "30f"; myStateHorizon[set] = devSettings ? "10s" : "10m"; setWidgetStates(set); break; case 3: // Debugger options { #ifdef DEBUGGER_SUPPORT uInt32 w = std::min(instance().frameBuffer().desktopSize().w, uInt32(DebuggerDialog::kMediumFontMinW)); uInt32 h = std::min(instance().frameBuffer().desktopSize().h, uInt32(DebuggerDialog::kMediumFontMinH)); myDebuggerWidthSlider->setValue(w); myDebuggerHeightSlider->setValue(h); myDebuggerFontSize->setSelected("medium"); myDebuggerFontStyle->setSelected("0"); myGhostReadsTrapWidget->setState(true); handleFontSize(); #endif break; } default: break; } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void DeveloperDialog::handleCommand(CommandSender* sender, int cmd, int data, int id) { switch(cmd) { case kPlrSettings: handleSettings(false); break; case kDevSettings: handleSettings(true); break; case kConsole: handleConsole(); break; case kTVJitter: handleTVJitterChange(myTVJitterWidget->getState()); break; case kTVJitterChanged: myTVJitterRecLabelWidget->setValue(myTVJitterRecWidget->getValue()); break; case kPPinCmd: instance().console().tia().driveUnusedPinsRandom(myUndrivenPinsWidget->getState()); break; case kTimeMachine: handleTimeMachine(); break; case kSizeChanged: handleSize(); break; case kUncompressedChanged: handleUncompressed(); break; case kIntervalChanged: handleInterval(); break; case kHorizonChanged: handleHorizon(); break; case kP0ColourChangedCmd: handleDebugColours(0, myDbgColour[0]->getSelected()); break; case kM0ColourChangedCmd: handleDebugColours(1, myDbgColour[1]->getSelected()); break; case kP1ColourChangedCmd: handleDebugColours(2, myDbgColour[2]->getSelected()); break; case kM1ColourChangedCmd: handleDebugColours(3, myDbgColour[3]->getSelected()); break; case kPFColourChangedCmd: handleDebugColours(4, myDbgColour[4]->getSelected()); break; case kBLColourChangedCmd: handleDebugColours(5, myDbgColour[5]->getSelected()); break; #ifdef DEBUGGER_SUPPORT case kDFontSizeChanged: handleFontSize(); break; #endif case GuiObject::kOKCmd: saveConfig(); close(); break; case GuiObject::kCloseCmd: // Revert changes made to event mapping close(); break; case GuiObject::kDefaultsCmd: setDefaults(); break; default: Dialog::handleCommand(sender, cmd, data, 0); } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void DeveloperDialog::handleSettings(bool devSettings) { if (mySettings != devSettings) { mySettings = devSettings; // block redundant events first! mySettingsGroup0->setSelected(devSettings ? 1 : 0); mySettingsGroup1->setSelected(devSettings ? 1 : 0); mySettingsGroup2->setSelected(devSettings ? 1 : 0); getWidgetStates(devSettings ? SettingsSet::player : SettingsSet::developer); setWidgetStates(devSettings ? SettingsSet::developer : SettingsSet::player); } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void DeveloperDialog::handleTVJitterChange(bool enable) { myTVJitterRecWidget->setEnabled(enable); myTVJitterRecLabelWidget->setEnabled(enable); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void DeveloperDialog::handleEnableDebugColors() { if(instance().hasConsole()) { bool fixed = instance().console().tia().usingFixedColors(); if(fixed != myDebugColorsWidget->getState()) instance().console().tia().toggleFixedColors(); } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void DeveloperDialog::handleConsole() { bool is7800 = myConsoleWidget->getSelected() == 1; myRandomizeRAMWidget->setEnabled(!is7800); if(is7800) myRandomizeRAMWidget->setState(false); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void DeveloperDialog::handleTimeMachine() { bool enable = myTimeMachineWidget->getState(); myStateSizeWidget->setEnabled(enable); myUncompressedWidget->setEnabled(enable); myStateIntervalWidget->setEnabled(enable); uInt32 size = myStateSizeWidget->getValue(); uInt32 uncompressed = myUncompressedWidget->getValue(); myStateHorizonWidget->setEnabled(enable && size > uncompressed); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void DeveloperDialog::handleSize() { uInt32 size = myStateSizeWidget->getValue(); uInt32 uncompressed = myUncompressedWidget->getValue(); Int32 interval = myStateIntervalWidget->getSelected(); Int32 horizon = myStateHorizonWidget->getSelected(); bool found = false; Int32 i; // handle illegal values if(interval == -1) interval = 0; if(horizon == -1) horizon = 0; // adapt horizon and interval do { for(i = horizon; i < NUM_HORIZONS; ++i) { if(uInt64(size) * instance().state().rewindManager().INTERVAL_CYCLES[interval] <= instance().state().rewindManager().HORIZON_CYCLES[i]) { found = true; break; } } if(!found) interval--; } while(!found); if(size < uncompressed) myUncompressedWidget->setValue(size); myStateIntervalWidget->setSelectedIndex(interval); myStateHorizonWidget->setSelectedIndex(i); myStateHorizonWidget->setEnabled(myTimeMachineWidget->getState() && size > uncompressed); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void DeveloperDialog::handleUncompressed() { uInt32 size = myStateSizeWidget->getValue(); uInt32 uncompressed = myUncompressedWidget->getValue(); if(size < uncompressed) myStateSizeWidget->setValue(uncompressed); myStateHorizonWidget->setEnabled(myTimeMachineWidget->getState() && size > uncompressed); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void DeveloperDialog::handleInterval() { uInt32 size = myStateSizeWidget->getValue(); uInt32 uncompressed = myUncompressedWidget->getValue(); Int32 interval = myStateIntervalWidget->getSelected(); Int32 horizon = myStateHorizonWidget->getSelected(); bool found = false; Int32 i; // handle illegal values if(interval == -1) interval = 0; if(horizon == -1) horizon = 0; // adapt horizon and size do { for(i = horizon; i < NUM_HORIZONS; ++i) { if(uInt64(size) * instance().state().rewindManager().INTERVAL_CYCLES[interval] <= instance().state().rewindManager().HORIZON_CYCLES[i]) { found = true; break; } } if(!found) size -= myStateSizeWidget->getStepValue(); } while(!found); myStateHorizonWidget->setSelectedIndex(i); myStateSizeWidget->setValue(size); if(size < uncompressed) myUncompressedWidget->setValue(size); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void DeveloperDialog::handleHorizon() { uInt32 size = myStateSizeWidget->getValue(); uInt32 uncompressed = myUncompressedWidget->getValue(); Int32 interval = myStateIntervalWidget->getSelected(); Int32 horizon = myStateHorizonWidget->getSelected(); bool found = false; Int32 i; // handle illegal values if(interval == -1) interval = 0; if(horizon == -1) horizon = 0; // adapt interval and size do { for(i = interval; i >= 0; --i) { if(uInt64(size) * instance().state().rewindManager().INTERVAL_CYCLES[i] <= instance().state().rewindManager().HORIZON_CYCLES[horizon]) { found = true; break; } } if(!found) size -= myStateSizeWidget->getStepValue(); } while(!found); myStateIntervalWidget->setSelectedIndex(i); myStateSizeWidget->setValue(size); if(size < uncompressed) myUncompressedWidget->setValue(size); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void DeveloperDialog::handleDebugColours(int idx, int color) { if(idx < 0 || idx >= DEBUG_COLORS) return; if(!instance().hasConsole()) { myDbgColour[idx]->clearFlags(WIDGET_ENABLED); myDbgColourSwatch[idx]->clearFlags(WIDGET_ENABLED); return; } static constexpr int dbg_color[2][DEBUG_COLORS] = { { TIA::FixedColor::NTSC_RED, TIA::FixedColor::NTSC_ORANGE, TIA::FixedColor::NTSC_YELLOW, TIA::FixedColor::NTSC_GREEN, TIA::FixedColor::NTSC_PURPLE, TIA::FixedColor::NTSC_BLUE }, { TIA::FixedColor::PAL_RED, TIA::FixedColor::PAL_ORANGE, TIA::FixedColor::PAL_YELLOW, TIA::FixedColor::PAL_GREEN, TIA::FixedColor::PAL_PURPLE, TIA::FixedColor::PAL_BLUE } }; int mode = instance().console().tia().frameLayout() == FrameLayout::ntsc ? 0 : 1; myDbgColourSwatch[idx]->setColor(dbg_color[mode][color]); myDbgColour[idx]->setSelectedIndex(color); // make sure the selected debug colors are all different bool usedCol[DEBUG_COLORS]; // identify used colors for(int i = 0; i < DEBUG_COLORS; ++i) { usedCol[i] = false; for(int j = 0; j < DEBUG_COLORS; ++j) { if(myDbgColourSwatch[j]->getColor() == dbg_color[mode][i]) { usedCol[i] = true; break; } } } // check if currently changed color was used somewhere else for(int i = 0; i < DEBUG_COLORS; ++i) { if (i != idx && myDbgColourSwatch[i]->getColor() == dbg_color[mode][color]) { // if already used, change the other color to an unused one for(int j = 0; j < DEBUG_COLORS; ++j) { if(!usedCol[j]) { myDbgColourSwatch[i]->setColor(dbg_color[mode][j]); myDbgColour[i]->setSelectedIndex(j); break; } } } } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void DeveloperDialog::handleDebugColours(const string& colors) { for(int i = 0; i < DEBUG_COLORS; ++i) { switch(colors[i]) { case 'r': handleDebugColours(i, 0); break; case 'o': handleDebugColours(i, 1); break; case 'y': handleDebugColours(i, 2); break; case 'g': handleDebugColours(i, 3); break; case 'p': handleDebugColours(i, 4); break; case 'b': handleDebugColours(i, 5); break; default: break; } } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void DeveloperDialog::handleFontSize() { #ifdef DEBUGGER_SUPPORT uInt32 minW, minH; int fontSize = myDebuggerFontSize->getSelected(); if(fontSize == 0) { minW = DebuggerDialog::kSmallFontMinW; minH = DebuggerDialog::kSmallFontMinH; } else if(fontSize == 1) { minW = DebuggerDialog::kMediumFontMinW; minH = DebuggerDialog::kMediumFontMinH; } else // large { minW = DebuggerDialog::kLargeFontMinW; minH = DebuggerDialog::kLargeFontMinH; } minW = std::min(instance().frameBuffer().desktopSize().w, minW); minH = std::min(instance().frameBuffer().desktopSize().h, minH); myDebuggerWidthSlider->setMinValue(minW); if(minW > uInt32(myDebuggerWidthSlider->getValue())) myDebuggerWidthSlider->setValue(minW); myDebuggerHeightSlider->setMinValue(minH); if(minH > uInt32(myDebuggerHeightSlider->getValue())) myDebuggerHeightSlider->setValue(minH); #endif }