handle quit/close events across SDL and wx sides including asking to save changes; refactor to remove old unused event loop code

This commit is contained in:
Christopher A. Mosher 2022-12-12 00:52:19 -05:00
parent 49d6e664f5
commit 6a52aadfe4
6 changed files with 89 additions and 86 deletions

View File

@ -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;
}

View File

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

View File

@ -24,8 +24,11 @@
#include "gui.h"
#include <wx/menu.h>
#include <wx/msgdlg.h>
#include <wx/event.h>
#include <wx/persist/toplevel.h>
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();
}
}

View File

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

View File

@ -16,6 +16,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#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)
}

View File

@ -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();
};