//============================================================================
//
//   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-2020 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"
#include "Console.hxx"
#include "TIA.hxx"
#include "OSystem.hxx"
#include "EventHandler.hxx"
#include "StateManager.hxx"
#include "RewindManager.hxx"
#include "M6502.hxx"
#ifdef DEBUGGER_SUPPORT
  #include "Debugger.hxx"
  #include "CartDebug.hxx"
  #include "DebuggerDialog.hxx"
#endif
#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
  setSize(54 * fontWidth + 10, 16 * (lineHeight + VGAP) + 14 + _th, max_w, 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);
  addTiaTab(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 ", TabWidget::AUTO_WIDTH);

  // settings set
  mySettingsGroupEmulation = new RadioButtonGroup();
  RadioButtonWidget* r = new RadioButtonWidget(myTab, font, HBORDER, ypos + 1,
                                               "Player settings", mySettingsGroupEmulation, kPlrSettings);
  wid.push_back(r);
  ypos += lineHeight + VGAP;
  r = new RadioButtonWidget(myTab, font, HBORDER, ypos + 1,
                            "Developer settings", mySettingsGroupEmulation, kDevSettings);
  wid.push_back(r);
  ypos += lineHeight + VGAP * 1;

  myFrameStatsWidget = new CheckboxWidget(myTab, font, HBORDER + INDENT * 1, ypos + 1, "Console info overlay");
  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
  myRandomizeCPULabel = new StaticTextWidget(myTab, font, HBORDER + INDENT * 2, ypos + 1, "Randomize CPU ");
  wid.push_back(myRandomizeCPULabel);

  int xpos = myRandomizeCPULabel->getRight() + 10;
  for(int i = 0; i < 5; ++i)
  {
    myRandomizeCPUWidget[i] = new CheckboxWidget(myTab, font, xpos, ypos + 1,
                                           ourCPUregs[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;

#ifdef DEBUGGER_SUPPORT
  myRWPortBreakWidget = new CheckboxWidget(myTab, font, HBORDER + INDENT * 1, ypos + 1,
                                           "Break on reads from write ports");
  wid.push_back(myRWPortBreakWidget);
  ypos += lineHeight + VGAP;

  myWRPortBreakWidget = new CheckboxWidget(myTab, font, HBORDER + INDENT * 1, ypos + 1,
                                           "Break on writes to read ports");
  wid.push_back(myWRPortBreakWidget);
  ypos += lineHeight + VGAP;
#endif

  // 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::addTiaTab(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 pwidth = font.getStringWidth("Faulty Cosmic Ark stars");
  WidgetArray wid;
  VariantList items;
  int tabID = myTab->addTab("  TIA  ", TabWidget::AUTO_WIDTH);

  wid.clear();

  // settings set
  mySettingsGroupTia = new RadioButtonGroup();
  RadioButtonWidget* r = new RadioButtonWidget(myTab, font, HBORDER, ypos + 1,
                                               "Player settings", mySettingsGroupTia, kPlrSettings);
  wid.push_back(r);
  ypos += lineHeight + VGAP;
  r = new RadioButtonWidget(myTab, font, HBORDER, ypos + 1,
                            "Developer settings", mySettingsGroupTia, kDevSettings);
  wid.push_back(r);
  ypos += lineHeight + VGAP * 1;

  items.clear();
  VarList::push_back(items, "Standard", "standard");
  VarList::push_back(items, "Faulty Kool-Aid Man", "koolaidman");
  VarList::push_back(items, "Faulty Cosmic Ark stars", "cosmicark");
  VarList::push_back(items, "Glitched Pesco", "pesco");
  VarList::push_back(items, "Glitched Quick Step!", "quickstep");
  VarList::push_back(items, "Glitched He-Man title", "heman");
  VarList::push_back(items, "Custom", "custom");
  myTIATypeWidget = new PopUpWidget(myTab, font, HBORDER + INDENT, ypos - 1,
                                    pwidth, lineHeight, items, "Chip type ", 0, kTIAType);
  wid.push_back(myTIATypeWidget);
  ypos += lineHeight + VGAP * 1;

  myInvPhaseLabel = new StaticTextWidget(myTab, font, HBORDER + INDENT * 2, ypos + 1,
                                         "Inverted HMOVE clock phase for");
  wid.push_back(myInvPhaseLabel);
  ypos += lineHeight + VGAP * 1;

  myPlInvPhaseWidget = new CheckboxWidget(myTab, font, HBORDER + INDENT * 3, ypos + 1,
                                          "Players");
  wid.push_back(myPlInvPhaseWidget);

  myMsInvPhaseWidget = new CheckboxWidget(myTab, font, myPlInvPhaseWidget->getRight() + 20, ypos + 1,
                                          "Missiles");
  wid.push_back(myMsInvPhaseWidget);

  myBlInvPhaseWidget = new CheckboxWidget(myTab, font, myMsInvPhaseWidget->getRight() + 20, ypos + 1,
                                          "Ball");
  wid.push_back(myBlInvPhaseWidget);
  ypos += lineHeight + VGAP * 1;

  myPlayfieldLabel = new StaticTextWidget(myTab, font, HBORDER + INDENT * 2, ypos + 1,
                                         "Delayed playfield");
  wid.push_back(myPlayfieldLabel);
  ypos += lineHeight + VGAP * 1;

  myPFBitsWidget = new CheckboxWidget(myTab, font, HBORDER + INDENT * 3, ypos + 1, "Bits");
  wid.push_back(myPFBitsWidget);
  //ypos += lineHeight + VGAP * 1;

  myPFColorWidget = new CheckboxWidget(myTab, font, myPFBitsWidget->getRight() + 20, ypos + 1, "Color");
  wid.push_back(myPFColorWidget);
  ypos += lineHeight + VGAP * 1;

  mySwapLabel = new StaticTextWidget(myTab, font, HBORDER + INDENT * 2, ypos + 1,
                                     "Delayed VDEL" + ELLIPSIS + " swap for");
  wid.push_back(mySwapLabel);
  ypos += lineHeight + VGAP * 1;

  myPlSwapWidget = new CheckboxWidget(myTab, font, HBORDER + INDENT * 3, ypos + 1, "Players");
  wid.push_back(myPlSwapWidget);

  myBlSwapWidget = new CheckboxWidget(myTab, font, myPlSwapWidget->getRight() + 20, ypos + 1, "Ball");
  wid.push_back(myBlSwapWidget);

  // Add items for tab 2
  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 ", TabWidget::AUTO_WIDTH);

  wid.clear();

  // settings set
  mySettingsGroupVideo = new RadioButtonGroup();
  RadioButtonWidget* r = new RadioButtonWidget(myTab, font, HBORDER, ypos + 1,
                                               "Player settings", mySettingsGroupVideo, kPlrSettings);
  wid.push_back(r);
  ypos += lineHeight + VGAP;
  r = new RadioButtonWidget(myTab, font, HBORDER, ypos + 1,
                            "Developer settings", mySettingsGroupVideo, 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->setTickmarkIntervals(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 std::array<int, DEBUG_COLORS> dbg_cmds = {
    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 std::array<string, NUM_INTERVALS> INTERVALS = {
    " 1 frame",
    " 3 frames",
    "10 frames",
    "30 frames",
    " 1 second",
    " 3 seconds",
    "10 seconds"
  };
  const std::array<string, NUM_INTERVALS> INT_SETTINGS = {
    "1f",
    "3f",
    "10f",
    "30f",
    "1s",
    "3s",
    "10s"
  };
  const std::array<string, NUM_HORIZONS> HORIZONS = {
    " 3 seconds",
    "10 seconds",
    "30 seconds",
    " 1 minute",
    " 3 minutes",
    "10 minutes",
    "30 minutes",
    "60 minutes"
  };
  const std::array<string, NUM_HORIZONS> HOR_SETTINGS = {
    "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 ", TabWidget::AUTO_WIDTH);

  // settings set
  mySettingsGroupTM = new RadioButtonGroup();
  RadioButtonWidget* r = new RadioButtonWidget(myTab, font, HBORDER, ypos + 1,
                                               "Player settings", mySettingsGroupTM, kPlrSettings);
  wid.push_back(r);
  ypos += lineHeight + VGAP;
  r = new RadioButtonWidget(myTab, font, HBORDER, ypos + 1,
                            "Developer settings", mySettingsGroupTM, 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);
#ifdef RETRON77
  myStateSizeWidget->setMaxValue(100);
#else
  myStateSizeWidget->setMaxValue(1000);
#endif
  myStateSizeWidget->setStepValue(20);
  myStateSizeWidget->setTickmarkIntervals(5);
  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);
#ifdef RETRON77
  myUncompressedWidget->setMaxValue(100);
#else
  myUncompressedWidget->setMaxValue(1000);
#endif
  myUncompressedWidget->setStepValue(20);
  myUncompressedWidget->setTickmarkIntervals(5);
  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);

  ypos += lineHeight + VGAP * 2;
  new StaticTextWidget(myTab, font, HBORDER, ypos + 1,
    "When entering/exiting emulation:");
  ypos += lineHeight + VGAP;
  mySaveOnExitGroup = new RadioButtonGroup();
  r = new RadioButtonWidget(myTab, font, HBORDER + INDENT, ypos + 1,
    "Do nothing", mySaveOnExitGroup);
  wid.push_back(r);
  ypos += lineHeight + VGAP;
  r = new RadioButtonWidget(myTab, font, HBORDER + INDENT, ypos + 1,
    "Save current state in current slot", mySaveOnExitGroup);
  wid.push_back(r);
  ypos += lineHeight + VGAP;
  r = new RadioButtonWidget(myTab, font, HBORDER + INDENT, ypos + 1,
    "Load/save all Time Machine states", mySaveOnExitGroup);
  wid.push_back(r);
  ypos += lineHeight + VGAP;


  myAutoSlotWidget = new CheckboxWidget(myTab, font, HBORDER, ypos + 1, "Automatically change save state slots");
  wid.push_back(myAutoSlotWidget);
  ypos += lineHeight + VGAP;

  // 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 ", TabWidget::AUTO_WIDTH);
  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 Common::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);
  // one tickmark every ~100 pixel
  myDebuggerWidthSlider->setTickmarkIntervals((ds.w - DebuggerDialog::kSmallFontMinW + 50) / 100);
  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);
  // one tickmark every ~100 pixel
  myDebuggerHeightSlider->setTickmarkIntervals((ds.h - DebuggerDialog::kSmallFontMinH + 50) / 100);
  wid.push_back(myDebuggerHeightSlider);
  ypos += lineHeight + VGAP * 4;

  myGhostReadsTrapWidget = new CheckboxWidget(myTab, font, HBORDER, ypos + 1,
                                             "Trap on 'ghost' reads");
  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");

#if defined(DEBUGGER_SUPPORT) && defined(WINDOWED_SUPPORT)
  // Debugger is only realistically available in windowed modes 800x600 or greater
  // (and when it's actually been compiled into the app)
  if(ds.w < 800 || ds.h < 600)  // TODO - maybe this logic can disappear?
  {
    myDebuggerWidthSlider->clearFlags(Widget::FLAG_ENABLED);
    myDebuggerHeightSlider->clearFlags(Widget::FLAG_ENABLED);
  }
#endif
#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)
{
  bool devSettings = set == SettingsSet::developer;
  const string& prefix = devSettings ? "dev." : "plr.";

  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] = devSettings ? instance().settings().getBool("dev.tiadriven") : false;
#ifdef DEBUGGER_SUPPORT
  // Read from write ports break
  myRWPortBreak[set] = devSettings ? instance().settings().getBool("dev.rwportbreak") : false;
  // Write to read ports break
  myWRPortBreak[set] = devSettings ? instance().settings().getBool("dev.wrportbreak") : false;
#endif
  // Thumb ARM emulation exception
  myThumbException[set] = devSettings ? instance().settings().getBool("dev.thumb.trapfatal") : false;
  // AtariVox/SaveKey EEPROM access
  myEEPROMAccess[set] = instance().settings().getBool(prefix + "eepromaccess");

  // TIA tab
  myTIAType[set] = devSettings ? instance().settings().getString("dev.tia.type") : "standard";
  myPlInvPhase[set] = devSettings ? instance().settings().getBool("dev.tia.plinvphase") : false;
  myMsInvPhase[set] = devSettings ? instance().settings().getBool("dev.tia.msinvphase") : false;
  myBlInvPhase[set] = devSettings ? instance().settings().getBool("dev.tia.blinvphase") : false;
  myPFBits[set] = devSettings ? instance().settings().getBool("dev.tia.delaypfbits") : false;
  myPFColor[set] = devSettings ? instance().settings().getBool("dev.tia.delaypfcolor") : false;
  myPlSwap[set] = devSettings ? instance().settings().getBool("dev.tia.delayplswap") : false;
  myBlSwap[set] = devSettings ? instance().settings().getBool("dev.tia.delayblswap") : false;

  // 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)
{
  bool devSettings = set == SettingsSet::developer;
  const string& prefix = devSettings ? "dev." : "plr.";

  instance().settings().setValue(prefix + "stats", myFrameStats[set]);
  instance().settings().setValue(prefix + "console", myConsole[set] == 1 ? "7800" : "2600");
  if(instance().hasConsole())
    instance().eventHandler().set7800Mode();

  // 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
  if(devSettings)
    instance().settings().setValue("dev.tiadriven", myUndrivenPins[set]);
#ifdef DEBUGGER_SUPPORT
  if (devSettings)
  {
    // Read from write ports break
    instance().settings().setValue("dev.rwportbreak", myRWPortBreak[set]);
    // Write to read ports break
    instance().settings().setValue("dev.wrportbreak", myWRPortBreak[set]);
  }
#endif
  if(devSettings)
    // Thumb ARM emulation exception
    instance().settings().setValue("dev.thumb.trapfatal", myThumbException[set]);
  // AtariVox/SaveKey EEPROM access
  instance().settings().setValue(prefix + "eepromaccess", myEEPROMAccess[set]);

  // TIA tab
  if (devSettings)
  {
    instance().settings().setValue("dev.tia.type", myTIAType[set]);
    if (BSPF::equalsIgnoreCase("custom", myTIAType[set]))
    {
      instance().settings().setValue("dev.tia.plinvphase", myPlInvPhase[set]);
      instance().settings().setValue("dev.tia.msinvphase", myMsInvPhase[set]);
      instance().settings().setValue("dev.tia.blinvphase", myBlInvPhase[set]);
      instance().settings().setValue("dev.tia.delaypfbits", myPFBits[set]);
      instance().settings().setValue("dev.tia.delaypfcolor", myPFColor[set]);
      instance().settings().setValue("dev.tia.delayplswap", myPlSwap[set]);
      instance().settings().setValue("dev.tia.delayblswap", myBlSwap[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;
  for(int i = 0; i < 5; ++i)
    if(myRandomizeCPUWidget[i]->getState())
      cpurandom += ourCPUregs[i];
  myRandomizeCPU[set] = cpurandom;
  // Undriven TIA pins
  myUndrivenPins[set] = myUndrivenPinsWidget->getState();
#ifdef DEBUGGER_SUPPORT
  // Read from write ports break
  myRWPortBreak[set] = myRWPortBreakWidget->getState();
  myWRPortBreak[set] = myWRPortBreakWidget->getState();
#endif
  // Thumb ARM emulation exception
  myThumbException[set] = myThumbExceptionWidget->getState();
  // AtariVox/SaveKey EEPROM access
  myEEPROMAccess[set] = myEEPROMAccessWidget->getState();

  // TIA tab
  myTIAType[set] = myTIATypeWidget->getSelectedTag().toString();
  myPlInvPhase[set] = myPlInvPhaseWidget->getState();
  myMsInvPhase[set] = myMsInvPhaseWidget->getState();
  myBlInvPhase[set] = myBlInvPhaseWidget->getState();
  myPFBits[set] = myPFBitsWidget->getState();
  myPFColor[set] = myPFColorWidget->getState();
  myPlSwap[set] = myPlSwapWidget->getState();
  myBlSwap[set] = myBlSwapWidget->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->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];
  for(int i = 0; i < 5; ++i)
    myRandomizeCPUWidget[i]->setState(BSPF::containsIgnoreCase(cpurandom, ourCPUregs[i]));
  // Undriven TIA pins
  myUndrivenPinsWidget->setState(myUndrivenPins[set]);
#ifdef DEBUGGER_SUPPORT
  // Read from write ports break
  myRWPortBreakWidget->setState(myRWPortBreak[set]);
  myWRPortBreakWidget->setState(myWRPortBreak[set]);
#endif
  // Thumb ARM emulation exception
  myThumbExceptionWidget->setState(myThumbException[set]);
  // AtariVox/SaveKey EEPROM access
  myEEPROMAccessWidget->setState(myEEPROMAccess[set]);
  handleConsole();

  // TIA tab
  myTIATypeWidget->setSelected(myTIAType[set], "standard");
  handleTia();

  // 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());

  // 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");
  handleSettings(devSettings);
  mySettings = devSettings;
  mySettingsGroupEmulation->setSelected(devSettings ? 1 : 0);
  mySettingsGroupTia->setSelected(devSettings ? 1 : 0);
  mySettingsGroupVideo->setSelected(devSettings ? 1 : 0);
  mySettingsGroupTM->setSelected(devSettings ? 1 : 0);

  // load both setting sets...
  loadSettings(SettingsSet::player);
  loadSettings(SettingsSet::developer);
  // ...and select the current one
  setWidgetStates(SettingsSet(mySettingsGroupEmulation->getSelected()));

  // Debug colours
  handleDebugColours(instance().settings().getString("tia.dbgcolors"));

  // Save on exit
  string saveOnExit = instance().settings().getString("saveonexit");
  mySaveOnExitGroup->setSelected(saveOnExit == "all" ? 2 : saveOnExit == "current" ? 1 : 0);
  // Automatically change save state slots
  myAutoSlotWidget->setState(instance().settings().getBool("autoslot"));

#ifdef DEBUGGER_SUPPORT
  uInt32 w, h;

  // Debugger size
  const Common::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", mySettingsGroupEmulation->getSelected() == SettingsSet::developer);
  // copy current widget status into set...
  getWidgetStates(SettingsSet(mySettingsGroupEmulation->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());
  }

  // TIA tab
  if(instance().hasConsole())
  {
    instance().console().tia().setPlInvertedPhaseClock(myPlInvPhaseWidget->getState());
    instance().console().tia().setMsInvertedPhaseClock(myMsInvPhaseWidget->getState());
    instance().console().tia().setBlInvertedPhaseClock(myBlInvPhaseWidget->getState());
    instance().console().tia().setPFBitsDelay(myPFBitsWidget->getState());
    instance().console().tia().setPFColorDelay(myPFColorWidget->getState());
    instance().console().tia().setPlSwapDelay(myPlSwapWidget->getState());
    instance().console().tia().setBlSwapDelay(myBlSwapWidget->getState());
  }

  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);

  // Save on exit
  int saveOnExit = mySaveOnExitGroup->getSelected();
  instance().settings().setValue("saveonexit",
    saveOnExit == 0 ? "none" : saveOnExit == 1 ? "current" : "all");
  // Automatically change save state slots
  instance().settings().setValue("autoslot", myAutoSlotWidget->getState());

#ifdef DEBUGGER_SUPPORT
  // Debugger font style
  instance().settings().setValue("dbg.fontstyle",
                                 myDebuggerFontStyle->getSelectedTag().toString());
  // Debugger size
  instance().settings().setValue("dbg.res",
                                 Common::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());

  // Read from write ports and write to read ports breaks
  if (instance().hasConsole())
  {
    instance().console().system().m6502().setReadFromWritePortBreak(myRWPortBreakWidget->getState());
    instance().console().system().m6502().setWriteToReadPortBreak(myWRPortBreakWidget->getState());
  }
#endif
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void DeveloperDialog::setDefaults()
{
  bool devSettings = mySettings;
  SettingsSet set = SettingsSet(mySettingsGroupEmulation->getSelected());

  switch(myTab->getActiveTab())
  {
    case 0: // Emulation
      myFrameStats[set] = devSettings ? true : false;
      myConsole[set] = 0;
      // Randomization
      myRandomBank[set] = devSettings ? true : false;
      myRandomizeRAM[set] = true;
      myRandomizeCPU[set] = devSettings ? "SAXYP" : "AXYP";
      // Undriven TIA pins
      myUndrivenPins[set] = devSettings ? true : false;
    #ifdef DEBUGGER_SUPPORT
      // Reads from write ports
      myRWPortBreak[set] = devSettings ? true : false;
      myWRPortBreak[set] = devSettings ? true : false;
    #endif
      // Thumb ARM emulation exception
      myThumbException[set] = devSettings ? true : false;
      // AtariVox/SaveKey EEPROM access
      myEEPROMAccess[set] = devSettings ? true : false;

      setWidgetStates(set);
      break;

    case 1: // TIA
      myTIAType[set] = "standard";
      // reset "custom" mode
      myPlInvPhase[set] = devSettings ? true : false;
      myMsInvPhase[set] = devSettings ? true : false;
      myBlInvPhase[set] = devSettings ? true : false;
      myPFBits[set] = devSettings ? true : false;
      myPFColor[set] = devSettings ? true : false;
      myPlSwap[set] = devSettings ? true : false;
      myBlSwap[set] = devSettings ? true : false;

      setWidgetStates(set);
      break;

    case 2: // 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 3: // States
      myTimeMachine[set] = true;
      myStateSize[set] = devSettings ? 1000 : 200;
      myUncompressed[set] = devSettings ? 600 : 60;
      myStateInterval[set] = devSettings ? "1f" : "30f";
      myStateHorizon[set] = devSettings ? "30s" : "10m";

      setWidgetStates(set);
      mySaveOnExitGroup->setSelected(0);
      myAutoSlotWidget->setState(false);
      break;

    case 4: // 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 kTIAType:
      handleTia();
      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)
{
  myUndrivenPinsWidget->setEnabled(devSettings);
#ifdef DEBUGGER_SUPPORT
  myRWPortBreakWidget->setEnabled(devSettings);
  myWRPortBreakWidget->setEnabled(devSettings);
#endif
  myThumbExceptionWidget->setEnabled(devSettings);

  if (mySettings != devSettings)
  {
    mySettings = devSettings; // block redundant events first!
    SettingsSet set = devSettings ? SettingsSet::developer : SettingsSet::player;
    mySettingsGroupEmulation->setSelected(set);
    mySettingsGroupTia->setSelected(set);
    mySettingsGroupVideo->setSelected(set);
    mySettingsGroupTM->setSelected(set);
    getWidgetStates(devSettings ? SettingsSet::player : SettingsSet::developer);
    setWidgetStates(set);
  }
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
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::handleTia()
{
  bool enable = BSPF::equalsIgnoreCase("custom", myTIATypeWidget->getSelectedTag().toString());

  myTIATypeWidget->setEnabled(mySettings);
  myInvPhaseLabel->setEnabled(enable);
  myPlInvPhaseWidget->setEnabled(enable);
  myMsInvPhaseWidget->setEnabled(enable);
  myBlInvPhaseWidget->setEnabled(enable);
  myPlayfieldLabel->setEnabled(enable);
  myPFBitsWidget->setEnabled(enable);
  myPFColorWidget->setEnabled(enable);
  mySwapLabel->setEnabled(enable);
  myPlSwapWidget->setEnabled(enable);
  myBlSwapWidget->setEnabled(enable);

  if(BSPF::equalsIgnoreCase("custom", myTIATypeWidget->getSelectedTag().toString()))
  {
    myPlInvPhaseWidget->setState(myPlInvPhase[SettingsSet::developer]);
    myMsInvPhaseWidget->setState(myMsInvPhase[SettingsSet::developer]);
    myBlInvPhaseWidget->setState(myBlInvPhase[SettingsSet::developer]);
    myPFBitsWidget->setState(myPFBits[SettingsSet::developer]);
    myPFColorWidget->setState(myPFColor[SettingsSet::developer]);
    myPlSwapWidget->setState(myPlSwap[SettingsSet::developer]);
    myBlSwapWidget->setState(myBlSwap[SettingsSet::developer]);
  }
  else
  {
    myPlInvPhaseWidget->setState(BSPF::equalsIgnoreCase("koolaidman", myTIATypeWidget->getSelectedTag().toString()));
    myMsInvPhaseWidget->setState(BSPF::equalsIgnoreCase("cosmicark", myTIATypeWidget->getSelectedTag().toString()));
    myBlInvPhaseWidget->setState(false);
    myPFBitsWidget->setState(BSPF::equalsIgnoreCase("pesco", myTIATypeWidget->getSelectedTag().toString()));
    myPFColorWidget->setState(BSPF::equalsIgnoreCase("quickstep", myTIATypeWidget->getSelectedTag().toString()));
    myPlSwapWidget->setState(BSPF::equalsIgnoreCase("heman", myTIATypeWidget->getSelectedTag().toString()));
    myBlSwapWidget->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::FLAG_ENABLED);
    myDbgColourSwatch[idx]->clearFlags(Widget::FLAG_ENABLED);
    return;
  }

  static constexpr BSPF::array2D<ColorId, 3, DEBUG_COLORS> dbg_color = {{
    {
      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
    },
    {
      TIA::FixedColor::SECAM_RED,
      TIA::FixedColor::SECAM_ORANGE,
      TIA::FixedColor::SECAM_YELLOW,
      TIA::FixedColor::SECAM_GREEN,
      TIA::FixedColor::SECAM_PURPLE,
      TIA::FixedColor::SECAM_BLUE
    }
  }};

  int timing = instance().console().timing() == ConsoleTiming::ntsc ? 0
    : instance().console().timing() == ConsoleTiming::pal ? 1 : 2;

  myDbgColourSwatch[idx]->setColor(dbg_color[timing][color]);
  myDbgColour[idx]->setSelectedIndex(color);

  // make sure the selected debug colors are all different
  std::array<bool, DEBUG_COLORS> usedCol;

  // 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[timing][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[timing][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[timing][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
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
const std::array<string, 5> DeveloperDialog::ourCPUregs = {
  "SP", "A", "X", "Y", "PS"
};
