//============================================================================ // // 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 "Cart.hxx" #include "Widget.hxx" #include "Dialog.hxx" #include "ToolTip.hxx" #include "Settings.hxx" #include "StellaKeys.hxx" #include "EventHandler.hxx" #include "TabWidget.hxx" #include "TiaInfoWidget.hxx" #include "TiaOutputWidget.hxx" #include "TiaZoomWidget.hxx" #include "AudioWidget.hxx" #include "PromptWidget.hxx" #include "CpuWidget.hxx" #include "RiotRamWidget.hxx" #include "RiotWidget.hxx" #include "RomWidget.hxx" #include "TiaWidget.hxx" #include "CartDebugWidget.hxx" #include "CartRamWidget.hxx" #include "DataGridOpsWidget.hxx" #include "EditTextWidget.hxx" #include "MessageBox.hxx" #include "Debugger.hxx" #include "DebuggerParser.hxx" #include "ConsoleFont.hxx" #include "ConsoleBFont.hxx" #include "ConsoleMediumFont.hxx" #include "ConsoleMediumBFont.hxx" #include "StellaMediumFont.hxx" #include "OptionsDialog.hxx" #include "BrowserDialog.hxx" #include "StateManager.hxx" #include "FrameManager.hxx" #include "OSystem.hxx" #include "Console.hxx" #include "DebuggerDialog.hxx" // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - DebuggerDialog::DebuggerDialog(OSystem& osystem, DialogContainer& parent, int x, int y, int w, int h) : Dialog(osystem, parent, x, y, w, h) { createFont(); // Font is sized according to available space addTiaArea(); addTabArea(); addStatusArea(); addRomArea(); // Inform the TIA output widget about its associated zoom widget myTiaOutput->setZoomWidget(myTiaZoom); myOptions = make_unique(osystem, parent, this, w, h, Menu::AppMode::debugger); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - DebuggerDialog::~DebuggerDialog() { } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void DebuggerDialog::loadConfig() { if(myFocusedWidget == nullptr) // Set initial focus to prompt tab myFocusedWidget = myPrompt; // Restore focus setFocus(myFocusedWidget); myTab->loadConfig(); myTiaInfo->loadConfig(); myTiaOutput->loadConfig(); myTiaZoom->loadConfig(); myCpu->loadConfig(); myRam->loadConfig(); myRomTab->loadConfig(); myMessageBox->setText(""); myMessageBox->setToolTip(""); } void DebuggerDialog::saveConfig() { myFocusedWidget = _focusedWidget; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void DebuggerDialog::handleKeyDown(StellaKey key, StellaMod mod, bool repeated) { if(key == KBDK_GRAVE && !StellaModTest::isShift(mod)) { // Swallow backtick, so we don't see it when exiting the debugger instance().eventHandler().enableTextEvents(false); } // special debugger keys first (cannot be remapped) if (StellaModTest::isControl(mod)) { switch (key) { case KBDK_S: doStep(); return; case KBDK_T: doTrace(); return; case KBDK_L: doScanlineAdvance(); return; case KBDK_F: doAdvance(); return; default: break; } } // handle emulation keys second (can be remapped) Event::Type event = instance().eventHandler().eventForKey(EventMode::kEmulationMode, key, mod); switch (event) { // events which can be handled 1:1 case Event::ToggleP0Collision: case Event::ToggleP0Bit: case Event::ToggleP1Collision: case Event::ToggleP1Bit: case Event::ToggleM0Collision: case Event::ToggleM0Bit: case Event::ToggleM1Collision: case Event::ToggleM1Bit: case Event::ToggleBLCollision: case Event::ToggleBLBit: case Event::TogglePFCollision: case Event::TogglePFBit: case Event::ToggleFixedColors: case Event::ToggleCollisions: case Event::ToggleBits: case Event::ToggleTimeMachine: case Event::SaveState: case Event::SaveAllStates: case Event::PreviousState : case Event::NextState: case Event::LoadState: case Event::LoadAllStates: case Event::ConsoleColor: case Event::ConsoleBlackWhite: case Event::ConsoleColorToggle: case Event::Console7800Pause: case Event::ConsoleLeftDiffA: case Event::ConsoleLeftDiffB: case Event::ConsoleLeftDiffToggle: case Event::ConsoleRightDiffA: case Event::ConsoleRightDiffB: case Event::ConsoleRightDiffToggle: instance().eventHandler().handleEvent(event); return; // events which need special handling in debugger case Event::TakeSnapshot: instance().debugger().parser().run("savesnap"); return; case Event::Rewind1Menu: doRewind(); return; case Event::Rewind10Menu: doRewind10(); return; case Event::RewindAllMenu: doRewindAll(); return; case Event::Unwind1Menu: doUnwind(); return; case Event::Unwind10Menu: doUnwind10(); return; case Event::UnwindAllMenu: doUnwindAll(); return; default: break; } Dialog::handleKeyDown(key, mod); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void DebuggerDialog::handleCommand(CommandSender* sender, int cmd, int data, int id) { // We reload the tabs in the cases where the actions could possibly // change their contents switch(cmd) { case kDDStepCmd: doStep(); break; case kDDTraceCmd: doTrace(); break; case kDDAdvCmd: doAdvance(); break; case kDDSAdvCmd: doScanlineAdvance(); break; case kDDRewindCmd: doRewind(); break; case kDDUnwindCmd: doUnwind(); break; case kDDRunCmd: doExitDebugger(); break; case kDDExitFatalCmd: doExitRom(); break; case kDDOptionsCmd: saveConfig(); myOptions->open(); loadConfig(); break; case kSvAccessCmd: runCommand("saveaccess"); break; case kSvDisCmd: runCommand("savedis"); break; case kSvRomCmd: runCommand("saverom"); break; case kSvScriptCmd: runCommand("save"); break; case kSvSessionCmd: runCommand("saveses"); break; case kBdCancelCmd: runCommand(); break; case RomWidget::kInvalidateListing: // Only do a full redraw if the disassembly tab is actually showing myRom->invalidate(myRomTab->getActiveTab() == 0); break; default: Dialog::handleCommand(sender, cmd, data, id); } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void DebuggerDialog::doStep() { instance().debugger().parser().run("step"); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void DebuggerDialog::doTrace() { instance().debugger().parser().run("trace"); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void DebuggerDialog::doAdvance() { instance().debugger().parser().run("frame #1"); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void DebuggerDialog::doScanlineAdvance() { instance().debugger().parser().run("scanline #1"); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void DebuggerDialog::doRewind() { instance().debugger().parser().run("rewind"); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void DebuggerDialog::doUnwind() { instance().debugger().parser().run("unwind"); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void DebuggerDialog::doRewind10() { instance().debugger().parser().run("rewind #10"); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void DebuggerDialog::doUnwind10() { instance().debugger().parser().run("unwind #10"); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void DebuggerDialog::doRewindAll() { instance().debugger().parser().run("rewind #1000"); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void DebuggerDialog::doUnwindAll() { instance().debugger().parser().run("unwind #1000"); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void DebuggerDialog::doExitDebugger() { instance().debugger().parser().run("run"); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void DebuggerDialog::doExitRom() { instance().debugger().parser().run("exitrom"); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void DebuggerDialog::createFont() { string fontSize = instance().settings().getString("dbg.fontsize"); int fontStyle = instance().settings().getInt("dbg.fontstyle"); if(fontSize == "large") { // Large font doesn't use fontStyle at all myLFont = make_unique(GUI::stellaMediumDesc); myNFont = make_unique(GUI::stellaMediumDesc); } else if(fontSize == "medium") { switch(fontStyle) { case 1: myLFont = make_unique(GUI::consoleMediumBDesc); myNFont = make_unique(GUI::consoleMediumDesc); break; case 2: myLFont = make_unique(GUI::consoleMediumDesc); myNFont = make_unique(GUI::consoleMediumBDesc); break; case 3: myLFont = make_unique(GUI::consoleMediumBDesc); myNFont = make_unique(GUI::consoleMediumBDesc); break; default: // default to zero myLFont = make_unique(GUI::consoleMediumDesc); myNFont = make_unique(GUI::consoleMediumDesc); break; } } else { switch(fontStyle) { case 1: myLFont = make_unique(GUI::consoleBDesc); myNFont = make_unique(GUI::consoleDesc); break; case 2: myLFont = make_unique(GUI::consoleDesc); myNFont = make_unique(GUI::consoleBDesc); break; case 3: myLFont = make_unique(GUI::consoleBDesc); myNFont = make_unique(GUI::consoleBDesc); break; default: // default to zero myLFont = make_unique(GUI::consoleDesc); myNFont = make_unique(GUI::consoleDesc); break; } } tooltip().setFont(*myNFont); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void DebuggerDialog::showBrowser(BrowserType type, const string& defaultName) { int cmd; string title; switch(type) { case BrowserType::svAccess: cmd = kSvAccessCmd; title = "access counters"; break; case BrowserType::svDis: cmd = kSvDisCmd; title = "disassembly"; break; case BrowserType::svRom: cmd = kSvRomCmd; title = "ROM"; break; case BrowserType::svScript: cmd = kSvScriptCmd; title = "workbench"; break; case BrowserType::svSession: cmd = kSvSessionCmd; title = "session"; break; default: cmd = 0; break; } if(cmd) { createBrowser("Save " + title + " as"); const string path = instance().userSaveDir().getPath() + defaultName; myBrowser->show(path, BrowserDialog::FileSave, cmd, kBdCancelCmd); } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void DebuggerDialog::runCommand(const string& command) { if(command != EmptyString) { FilesystemNode dir(myBrowser->getResult()); string result = instance().debugger().parser().run(command + " {" + dir.getShortPath() + "}"); prompt().print(result + '\n'); } prompt().printPrompt(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void DebuggerDialog::createBrowser(const string& title) { uInt32 w = 0, h = 0; getDynamicBounds(w, h); if(w > uInt32(_font.getMaxCharWidth() * 80)) w = _font.getMaxCharWidth() * 80; // Create file browser dialog if(!myBrowser || uInt32(myBrowser->getWidth()) != w || uInt32(myBrowser->getHeight()) != h) myBrowser = make_unique(this, instance().frameBuffer().font(), w, h, title); else myBrowser->setTitle(title); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void DebuggerDialog::showFatalMessage(const string& msg) { myFatalError = make_unique(this, *myLFont, msg, _w-20, _h-20, kDDExitFatalCmd, "Exit ROM", "Continue", "Fatal error"); myFatalError->show(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void DebuggerDialog::addTiaArea() { const Common::Rect& r = getTiaBounds(); myTiaOutput = new TiaOutputWidget(this, *myNFont, r.x(), r.y(), r.w(), r.h()); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void DebuggerDialog::addTabArea() { const Common::Rect& r = getTabBounds(); const int vBorder = 4; // The tab widget // Since there are two tab widgets in this dialog, we specifically // assign an ID of 0 myTab = new TabWidget(this, *myLFont, r.x(), r.y() + vBorder, r.w(), r.h() - vBorder); myTab->setID(0); addTabWidget(myTab); const int widWidth = r.w() - vBorder; const int widHeight = r.h() - myTab->getTabHeight() - vBorder - 4; int tabID; // The Prompt/console tab tabID = myTab->addTab("Prompt"); myPrompt = new PromptWidget(myTab, *myNFont, 2, 2, widWidth - 4, widHeight); myTab->setParentWidget(tabID, myPrompt); addToFocusList(myPrompt->getFocusList(), myTab, tabID); // The TIA tab tabID = myTab->addTab("TIA"); TiaWidget* tia = new TiaWidget(myTab, *myLFont, *myNFont, 2, 2, widWidth, widHeight); myTab->setParentWidget(tabID, tia); addToFocusList(tia->getFocusList(), myTab, tabID); // The input/output tab (includes RIOT and INPTx from TIA) tabID = myTab->addTab("I/O"); RiotWidget* riot = new RiotWidget(myTab, *myLFont, *myNFont, 2, 2, widWidth, widHeight); myTab->setParentWidget(tabID, riot); addToFocusList(riot->getFocusList(), myTab, tabID); // The Audio tab tabID = myTab->addTab("Audio"); AudioWidget* aud = new AudioWidget(myTab, *myLFont, *myNFont, 2, 2, widWidth, widHeight); myTab->setParentWidget(tabID, aud); addToFocusList(aud->getFocusList(), myTab, tabID); myTab->setActiveTab(0); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void DebuggerDialog::addStatusArea() { const int lineHeight = myLFont->getLineHeight(); const Common::Rect& r = getStatusBounds(); const int HBORDER = 10; const int VGAP = lineHeight / 3; int xpos, ypos; xpos = r.x() + HBORDER; ypos = r.y(); myTiaInfo = new TiaInfoWidget(this, *myLFont, *myNFont, xpos, ypos, r.w() - HBORDER); ypos = myTiaInfo->getBottom() + VGAP; myTiaZoom = new TiaZoomWidget(this, *myNFont, xpos, ypos, r.w() - HBORDER, r.h() - ypos - VGAP - lineHeight + 3); addToFocusList(myTiaZoom->getFocusList()); ypos = myTiaZoom->getBottom() + VGAP; myMessageBox = new EditTextWidget(this, *myLFont, xpos, ypos, myTiaZoom->getWidth(), lineHeight); myMessageBox->setEditable(false, false); myMessageBox->clearFlags(Widget::FLAG_RETAIN_FOCUS); myMessageBox->setTextColor(kTextColorEm); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void DebuggerDialog::addRomArea() { static constexpr std::array LEFT_ARROW = { 0b0000010, 0b0000110, 0b0001110, 0b0011110, 0b0111110, 0b1111110, 0b0111110, 0b0011110, 0b0001110, 0b0000110, 0b0000010 }; static constexpr std::array RIGHT_ARROW = { 0b0100000, 0b0110000, 0b0111000, 0b0111100, 0b0111110, 0b0111111, 0b0111110, 0b0111100, 0b0111000, 0b0110000, 0b0100000 }; const Common::Rect& r = getRomBounds(); const int VBORDER = 4; WidgetArray wid1, wid2; ButtonWidget* b; int bwidth = myLFont->getStringWidth("Frame +1 "), bheight = myLFont->getLineHeight() + 2; int buttonX = r.x() + r.w() - bwidth - 5, buttonY = r.y() + 5; b = new ButtonWidget(this, *myLFont, buttonX, buttonY, bwidth, bheight, "Step", kDDStepCmd, true); wid2.push_back(b); buttonY += bheight + 4; b = new ButtonWidget(this, *myLFont, buttonX, buttonY, bwidth, bheight, "Trace", kDDTraceCmd, true); wid2.push_back(b); buttonY += bheight + 4; b = new ButtonWidget(this, *myLFont, buttonX, buttonY, bwidth, bheight, "Scan +1", kDDSAdvCmd, true); wid2.push_back(b); buttonY += bheight + 4; b = new ButtonWidget(this, *myLFont, buttonX, buttonY, bwidth, bheight, "Frame +1", kDDAdvCmd, true); wid2.push_back(b); buttonY += bheight + 4; b = new ButtonWidget(this, *myLFont, buttonX, buttonY, bwidth, bheight, "Run", kDDRunCmd); wid2.push_back(b); addCancelWidget(b); bwidth = bheight; // 7 + 12; bheight = bheight * 3 + 4 * 2; buttonX -= (bwidth + 5); buttonY = r.y() + 5; myRewindButton = new ButtonWidget(this, *myLFont, buttonX, buttonY, bwidth, bheight, LEFT_ARROW.data(), 7, 11, kDDRewindCmd, true); myRewindButton->clearFlags(Widget::FLAG_ENABLED); buttonY += bheight + 4; bheight = (myLFont->getLineHeight() + 2) * 2 + 4 * 1; myUnwindButton = new ButtonWidget(this, *myLFont, buttonX, buttonY, bwidth, bheight, RIGHT_ARROW.data(), 7, 11, kDDUnwindCmd, true); myUnwindButton->clearFlags(Widget::FLAG_ENABLED); int xpos = buttonX - 8*myLFont->getMaxCharWidth() - 20, ypos = 30; bwidth = myLFont->getStringWidth("Options " + ELLIPSIS); bheight = myLFont->getLineHeight() + 2; b = new ButtonWidget(this, *myLFont, xpos, r.y() + 5, bwidth, bheight, "Options" + ELLIPSIS, kDDOptionsCmd); wid1.push_back(b); wid1.push_back(myRewindButton); wid1.push_back(myUnwindButton); DataGridOpsWidget* ops = new DataGridOpsWidget(this, *myLFont, xpos, ypos); int max_w = xpos - r.x() - 10; xpos = r.x() + 10; ypos = 5; myCpu = new CpuWidget(this, *myLFont, *myNFont, xpos, ypos, max_w); addToFocusList(myCpu->getFocusList()); addToFocusList(wid1); addToFocusList(wid2); xpos = r.x() + 10; ypos += myCpu->getHeight() + 10; myRam = new RiotRamWidget(this, *myLFont, *myNFont, xpos, ypos, r.w() - 10); addToFocusList(myRam->getFocusList()); // Add the DataGridOpsWidget to any widgets which contain a // DataGridWidget which we want controlled myCpu->setOpsWidget(ops); myRam->setOpsWidget(ops); //////////////////////////////////////////////////////////////////// // Disassembly area xpos = r.x() + VBORDER; ypos += myRam->getHeight() + 5; const int tabWidth = r.w() - VBORDER - 1; const int tabHeight = r.h() - ypos - 1; int tabID; // Since there are two tab widgets in this dialog, we specifically // assign an ID of 1 myRomTab = new TabWidget( this, *myLFont, xpos, ypos, tabWidth, tabHeight); myRomTab->setID(1); addTabWidget(myRomTab); // The main disassembly tab tabID = myRomTab->addTab(" Disassembly ", TabWidget::AUTO_WIDTH); myRom = new RomWidget(myRomTab, *myLFont, *myNFont, 2, 2, tabWidth - 1, tabHeight - myRomTab->getTabHeight() - 2); myRomTab->setParentWidget(tabID, myRom); addToFocusList(myRom->getFocusList(), myRomTab, tabID); // The 'cart-specific' information tab (optional) tabID = myRomTab->addTab(" " + instance().console().cartridge().name() + " ", TabWidget::AUTO_WIDTH); myCartInfo = instance().console().cartridge().infoWidget( myRomTab, *myLFont, *myNFont, 2, 2, tabWidth - 1, tabHeight - myRomTab->getTabHeight() - 2); if(myCartInfo != nullptr) { myRomTab->setParentWidget(tabID, myCartInfo); addToFocusList(myCartInfo->getFocusList(), myRomTab, tabID); tabID = myRomTab->addTab(" States ", TabWidget::AUTO_WIDTH); } // The 'cart-specific' state tab myCartDebug = instance().console().cartridge().debugWidget( myRomTab, *myLFont, *myNFont, 2, 2, tabWidth - 1, tabHeight - myRomTab->getTabHeight() - 2); if(myCartDebug) // TODO - make this always non-null { myRomTab->setParentWidget(tabID, myCartDebug); addToFocusList(myCartDebug->getFocusList(), myRomTab, tabID); // The cartridge RAM tab if (myCartDebug->internalRamSize() > 0) { tabID = myRomTab->addTab(myCartDebug->tabLabel(), TabWidget::AUTO_WIDTH); myCartRam = new CartRamWidget(myRomTab, *myLFont, *myNFont, 2, 2, tabWidth - 1, tabHeight - myRomTab->getTabHeight() - 2, *myCartDebug); if(myCartRam) // TODO - make this always non-null { myRomTab->setParentWidget(tabID, myCartRam); addToFocusList(myCartRam->getFocusList(), myRomTab, tabID); myCartRam->setOpsWidget(ops); } } } myRomTab->setActiveTab(0); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Common::Rect DebuggerDialog::getTiaBounds() const { // The area showing the TIA image (NTSC and PAL supported, up to 274 lines without scaling) return Common::Rect(0, 0, 320, std::max(int(FrameManager::Metrics::baseHeightPAL), int(_h * 0.35))); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Common::Rect DebuggerDialog::getRomBounds() const { // The ROM area is the full area to the right of the tabs const Common::Rect& status = getStatusBounds(); return Common::Rect(status.x() + status.w() + 1, 0, _w, _h); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Common::Rect DebuggerDialog::getStatusBounds() const { // The status area is the full area to the right of the TIA image // extending as far as necessary // 30% of any space above 1030 pixels will be allocated to this area const Common::Rect& tia = getTiaBounds(); int x1 = tia.x() + tia.w() + 1; int y1 = 0; int x2 = tia.x() + tia.w() + 225 + (_w > 1030 ? int(0.35 * (_w - 1030)) : 0); int y2 = tia.y() + tia.h(); return Common::Rect(x1, y1, x2, y2); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Common::Rect DebuggerDialog::getTabBounds() const { // The tab area is the full area below the TIA image const Common::Rect& tia = getTiaBounds(); const Common::Rect& status = getStatusBounds(); return Common::Rect(0, tia.y() + tia.h() + 1, status.x() + status.w() + 1, _h); }