From 6a52aadfe46013617e47f7b1d4133a0e0277def6 Mon Sep 17 00:00:00 2001 From: "Christopher A. Mosher" Date: Mon, 12 Dec 2022 00:52:19 -0500 Subject: [PATCH] handle quit/close events across SDL and wx sides including asking to save changes; refactor to remove old unused event loop code --- src/E2wxApp.cpp | 44 ++++++++++++++++++--- src/E2wxApp.h | 5 +++ src/E2wxFrame.cpp | 15 +++++++- src/E2wxFrame.h | 1 + src/emulator.cpp | 98 +++++++++++++---------------------------------- src/emulator.h | 12 ++---- 6 files changed, 89 insertions(+), 86 deletions(-) diff --git a/src/E2wxApp.cpp b/src/E2wxApp.cpp index a38314a..264cc6b 100644 --- a/src/E2wxApp.cpp +++ b/src/E2wxApp.cpp @@ -70,8 +70,9 @@ EmuTimer::EmuTimer(Emulator *e) : wxTimer(), emu(e) { EmuTimer::~EmuTimer() { } +#define EXPECTED_MS 50 // see CHECK_EVERY_CYCLE in Emulator.cpp void EmuTimer::begin() { - this->Start(50); // TODO: EXPECTED_MS from Emulator + this->Start(EXPECTED_MS); } void EmuTimer::Notify() { @@ -83,7 +84,10 @@ void EmuTimer::Notify() { E2wxApp::E2wxApp() : id(wxSTRINGIZE(PROJECT_VENDOR) "." wxSTRINGIZE(PROJECT_NAME)), - version(wxSTRINGIZE(PROJECT_VERSION)) { + version(wxSTRINGIZE(PROJECT_VERSION)), + frame(nullptr), + emu(nullptr), + emu_timer(nullptr) { } E2wxApp::~E2wxApp() { @@ -169,7 +173,7 @@ bool E2wxApp::OnInit() { - E2wxFrame *frame = new E2wxFrame(); + frame = new E2wxFrame(); frame->DoInit(); frame->Show(); @@ -184,12 +188,26 @@ bool E2wxApp::OnInit() { +bool E2wxApp::CloseMainFrame() { + bool r = false; + if (this->frame) { + if (this->frame->Close(false)) { + delete this->frame; + this->frame = nullptr; + r = true; + } + } + return r; +} + int E2wxApp::OnExit() { if (this->emu_timer) { delete this->emu_timer; + this->emu_timer = nullptr; } if (this->emu) { delete this->emu; + this->emu = nullptr; } return 0; } @@ -308,10 +326,26 @@ void E2wxApp::InitBoostLog() { void E2wxApp::StartEmulator() { + if (this->emu_timer) { + delete this->emu_timer; + } + if (this->emu) { + delete this->emu; + } + this->emu = new Emulator(); - E2Config cfg(this->arg_configfile, this->opt_config_from_prefs_only); + E2Config cfg{this->arg_configfile, this->opt_config_from_prefs_only}; this->emu->config(cfg); this->emu->init(); - this->emu_timer = new EmuTimer(this->emu); + + this->emu_timer = new EmuTimer{this->emu}; this->emu_timer->begin(); } + +bool E2wxApp::EnsureCanQuit() { + bool ok = true; + if (this->emu) { + ok = this->emu->isSafeToQuit(); + } + return ok; +} diff --git a/src/E2wxApp.h b/src/E2wxApp.h index 7b04115..0adf651 100644 --- a/src/E2wxApp.h +++ b/src/E2wxApp.h @@ -32,6 +32,7 @@ +class E2wxFrame; class Emulator; @@ -60,6 +61,7 @@ class E2wxApp : public wxApp { std::filesystem::path docsdir; std::filesystem::path arg_configfile; bool opt_config_from_prefs_only; + E2wxFrame *frame; EmuTimer *emu_timer; Emulator *emu; @@ -79,6 +81,9 @@ public: const std::filesystem::path GetConfigDir() const; const std::filesystem::path GetDocumentsDir() const; + bool CloseMainFrame(); + bool EnsureCanQuit(); + virtual bool OnInit() override; virtual int OnExit() override; virtual void OnFatalException() override; diff --git a/src/E2wxFrame.cpp b/src/E2wxFrame.cpp index 2e21ab6..946f462 100644 --- a/src/E2wxFrame.cpp +++ b/src/E2wxFrame.cpp @@ -24,8 +24,11 @@ #include "gui.h" #include #include +#include #include + + enum E2MenuID { ID_MENUITEM_POWER = wxID_HIGHEST+1 }; @@ -35,6 +38,7 @@ wxBEGIN_EVENT_TABLE(E2wxFrame, wxFrame) EVT_MENU(wxID_PREFERENCES, E2wxFrame::OnPreferences) EVT_MENU(wxID_ABOUT, E2wxFrame::OnAbout) EVT_MENU(ID_MENUITEM_POWER, E2wxFrame::OnTogglePower) + EVT_CLOSE(E2wxFrame::HandleUserQuitRequest) wxEND_EVENT_TABLE() @@ -84,7 +88,7 @@ void E2wxFrame::InitStatusBar() { void E2wxFrame::OnExit(wxCommandEvent& event) { - Close(true); + Close(false); } void E2wxFrame::OnAbout(wxCommandEvent& event) { @@ -109,3 +113,12 @@ void E2wxFrame::OnPreferences(wxCommandEvent& event) { void E2wxFrame::OnTogglePower(wxCommandEvent& event) { GUI::queueTogglePower(); } + +void E2wxFrame::HandleUserQuitRequest(wxCloseEvent& event) { + // TODO how to handle event.CanVeto() ? I'd like to auto-save everything + if (wxGetApp().EnsureCanQuit()) { + event.Skip(); + } else { + event.Veto(); + } +} diff --git a/src/E2wxFrame.h b/src/E2wxFrame.h index fdb09d2..ed09c6a 100644 --- a/src/E2wxFrame.h +++ b/src/E2wxFrame.h @@ -37,6 +37,7 @@ private: void OnPreferences(wxCommandEvent& event); void OnAbout(wxCommandEvent& event); void OnTogglePower(wxCommandEvent& event); + void HandleUserQuitRequest(wxCloseEvent& event); void InitMenuBar(); void InitStatusBar(); diff --git a/src/emulator.cpp b/src/emulator.cpp index 5c07927..06d79d7 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -16,6 +16,7 @@ along with this program. If not, see . */ #include "emulator.h" +#include "E2wxApp.h" #include "e2config.h" #include "e2command.h" #include "e2const.h" @@ -65,7 +66,6 @@ void Emulator::powerOnComputer() { } void Emulator::powerOffComputer() { - // TODO Need to ask user if OK to lose any unsaved changes to disks this->apple2.powerOff(); this->screenImage.drawPower(false); this->display.setNoise(true); @@ -85,26 +85,10 @@ void Emulator::init() { this->display.powerOn(true); } -// How many emulation ticks between asking SDL if there is any new input -// from the user or other GUI events. -// This is also how often we shall update the estimate of the emulator's -// actual speed performance -// When the CPU is the object being ticked (each tick is a CPU cycle), then -// this is 20.04378892 Hz in emulated seconds time -#define CHECK_EVERY_CYCLE 51024 -#define CHECK_CYCLES_K 51024000 -#define EXPECTED_MS 50 // U.A.2 p. 7-13: REPT key repeats at 10Hz. static const int CYCLES_PER_REPT(E2Const::AVG_CPU_HZ / 10); - - - - - - - // If the Apple ][ keyboard repeat is on (the REPT key is // down)... void Emulator::handleRepeatKey() { @@ -134,7 +118,9 @@ void Emulator::handleAnyPendingEvents() { switch (event.type) { case SDL_QUIT: // If SDL is going away... - quitIfSafe(); + // could be due to user closing the SDL window, pressing cmd-Q on Mac, + // ctrl-C from the command line on Linux, process being killed, etc. + handleUserQuitRequest(); break; case SDL_KEYDOWN: // If we're collecting a command line for changing any @@ -145,6 +131,7 @@ void Emulator::handleAnyPendingEvents() { // ...else we're collecting keypresses for the keyboard // emulation (and thus the Apple ][ emulation itself) dispatchKeypress(event.key); + // People who have too many press-releases should be referred to as "keyboards" break; case SDL_KEYUP: // If we're collecting a command line for changing any @@ -166,54 +153,15 @@ void Emulator::handleAnyPendingEvents() { +// How many emulation ticks between asking SDL if there is any new input +// from the user or other GUI events. +// This is also how often we shall update the estimate of the emulator's +// actual speed performance +// When the CPU is the object being ticked (each tick is a CPU cycle), then +// this is 20.04378892 Hz in emulated seconds time +#define CHECK_EVERY_CYCLE 51024 + // The core of this Apple -int Emulator::run() { - // While the user still wants to run this emulation... - while (!this->quit) { - tick(); - } - return 0; -} - - - - -void Emulator::tick() { - if (this->timable) { - this->timable->tick(); // this runs the emulator! - handleRepeatKey(); - } - - // People who have too many press releases should be referred to as - // keyboards - - if (CHECK_EVERY_CYCLE <= ++this->skip) { - // ...then it's time to drain away any piled-up user interaction - // events that SDL has stored up for us - // Reload the skip quantity - this->skip = 0; - - handleAnyPendingEvents(); - - // If we're trying to run as slow as a real Apple ][... - if (!this->fhyper.isHyper()) { - const int delta_ms = EXPECTED_MS - (SDL_GetTicks() - this->prev_ms); - if (0 < delta_ms && delta_ms <= EXPECTED_MS) { - SDL_Delay(delta_ms); - } - - } - - // Display the current estimate of the emulator's actual speed - // performance - this->screenImage.displayHz(CHECK_CYCLES_K / (SDL_GetTicks() - this->prev_ms)); - - this->prev_ms = SDL_GetTicks(); - } -} - - - void Emulator::tick50ms() { if (this->timable) { for (int i = 0; i < CHECK_EVERY_CYCLE; ++i) { @@ -222,7 +170,7 @@ void Emulator::tick50ms() { } } handleAnyPendingEvents(); - this->screenImage.displayHz(CHECK_CYCLES_K / (SDL_GetTicks() - this->prev_ms)); + this->screenImage.displayHz((1000*CHECK_EVERY_CYCLE)/(SDL_GetTicks() - this->prev_ms)); this->prev_ms = SDL_GetTicks(); // TODO: how to check this->quit ? } @@ -413,7 +361,7 @@ void Emulator::dispatchKeypress(const SDL_KeyboardEvent& keyEvent) { return; }// ...else exit the entire emulation else if (sym == SDLK_F9) { - quitIfSafe(); + handleUserQuitRequest(); return; }// ...else save a screen shot else if (sym == SDLK_F8) { @@ -495,7 +443,10 @@ static int askSave() { } bool Emulator::isSafeToQuit() { + this->screenImage.exitFullScreen(); + if (!this->apple2.cassetteOut.eject()) { + // TODO does this handle the case where we fail to save? return false; } @@ -512,14 +463,17 @@ bool Emulator::isSafeToQuit() { if (resp == wxID_YES) { this->apple2.slts.save(0); this->apple2.slts.save(1); + // TODO handle case where we fail to save, + // in which case we should alert the user, + // and return false } return true; } -void Emulator::quitIfSafe() { - this->screenImage.exitFullScreen(); - if (isSafeToQuit()) { - this->quit = true; - } +// we come here due to F9 or SQL_Quit event +void Emulator::handleUserQuitRequest() { + wxGetApp().CloseMainFrame(); + // wxWidgets will then call us back with isSafeToQuit, and if so will exit the application + // (note wxWidgets will also need to call us back when it gets it's own quit requests) } diff --git a/src/emulator.h b/src/emulator.h index 308358c..fe690d3 100644 --- a/src/emulator.h +++ b/src/emulator.h @@ -32,8 +32,7 @@ class Timable; class E2Config; -class Emulator -{ +class Emulator { PaddleButtonStates paddleButtonStates; KeypressQueue keypresses; @@ -58,16 +57,14 @@ class Emulator bool pendingCommandExit; std::string cmdline; - void tick(); - void dispatchKeypress(const SDL_KeyboardEvent& keyEvent); void dispatchKeyUp(const SDL_KeyboardEvent& keyEvent); void cmdKey(const SDL_KeyboardEvent& keyEvent); void processCommand(); - bool isSafeToQuit(); void handleRepeatKey(); void handleAnyPendingEvents(); + void handleUserQuitRequest(); public: Emulator(); @@ -75,15 +72,14 @@ public: void config(E2Config& cfg); - virtual void init(); + void init(); void powerOnComputer(); void powerOffComputer(); void toggleComputerPower(); void cycleDisplayType(); - void quitIfSafe(); + bool isSafeToQuit(); - virtual int run(); void tick50ms(); };