From 29165a568594a24177c21b6ecf8e568b599dad99 Mon Sep 17 00:00:00 2001 From: Chris Mosher Date: Mon, 19 Dec 2022 09:40:54 +0900 Subject: [PATCH] make screenimage be a wxFrame; break out REPT key handling into dedicated class; progress on wx paddle handling --- CMakeLists.txt | 7 +++- src/E2wxApp.h | 2 + src/E2wxFrame.cpp | 1 + src/KeyEventHandler.cpp | 36 +++++++++++++++++ src/KeyEventHandler.h | 39 ++++++++++++++++++ src/KeyRepeatHandler.cpp | 79 +++++++++++++++++++++++++++++++++++++ src/KeyRepeatHandler.h | 52 ++++++++++++++++++++++++ src/emulator.cpp | 61 ++++++++++------------------ src/emulator.h | 7 ++-- src/paddles.cpp | 85 ++++++++++++++++++++++------------------ src/screenimage.cpp | 42 ++++++++++++++++++-- src/screenimage.h | 10 ++++- 12 files changed, 332 insertions(+), 89 deletions(-) create mode 100644 src/KeyEventHandler.cpp create mode 100644 src/KeyEventHandler.h create mode 100644 src/KeyRepeatHandler.cpp create mode 100644 src/KeyRepeatHandler.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 8484731..cb88506 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -91,6 +91,8 @@ firmwarecard.cpp gui.cpp keyboardbuffermode.cpp keyboard.cpp +KeyEventHandler.cpp +KeyRepeatHandler.cpp languagecard.cpp lss.cpp magneticfield.cpp @@ -201,7 +203,10 @@ include_directories(${PROJECT_BINARY_DIR}) # TODO: can we remove this without being too backwardly incompatible? target_compile_definitions(${APP_NAME} PRIVATE ETCDIR="${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_SYSCONFDIR}") - +if(WIN32) +else() + target_compile_options(${APP_NAME} PRIVATE -ggdb3) +endif() set_target_properties(${APP_NAME} PROPERTIES RESOURCE "${resources}") diff --git a/src/E2wxApp.h b/src/E2wxApp.h index 1c6de26..72f0532 100644 --- a/src/E2wxApp.h +++ b/src/E2wxApp.h @@ -83,6 +83,8 @@ public: const std::filesystem::path GetConfigDir() const; const std::filesystem::path GetDocumentsDir() const; + E2wxFrame *GetFrame() { return this->frame; } + void StartEmulator(); void StopEmulator(); diff --git a/src/E2wxFrame.cpp b/src/E2wxFrame.cpp index a17d4d0..f6410d0 100644 --- a/src/E2wxFrame.cpp +++ b/src/E2wxFrame.cpp @@ -31,6 +31,7 @@ #include +#include enum E2MenuID { diff --git a/src/KeyEventHandler.cpp b/src/KeyEventHandler.cpp new file mode 100644 index 0000000..fd2101e --- /dev/null +++ b/src/KeyEventHandler.cpp @@ -0,0 +1,36 @@ +/* + epple2 + Copyright (C) 2022 by Christopher A. Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +/* + * File: KeyEventHandler.cpp + * Author: chris.mosher + * + * Created on December 19, 2022, 7:36 AM + */ + +#include "KeyEventHandler.h" + +KeyEventHandler::KeyEventHandler() { +} + +KeyEventHandler::KeyEventHandler(const KeyEventHandler& orig) { +} + +KeyEventHandler::~KeyEventHandler() { +} + diff --git a/src/KeyEventHandler.h b/src/KeyEventHandler.h new file mode 100644 index 0000000..7e2bca4 --- /dev/null +++ b/src/KeyEventHandler.h @@ -0,0 +1,39 @@ +/* + epple2 + Copyright (C) 2022 by Christopher A. Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +/* + * File: KeyEventHandler.h + * Author: chris.mosher + * + * Created on December 19, 2022, 7:36 AM + */ + +#ifndef KEYEVENTHANDLER_H +#define KEYEVENTHANDLER_H + +class KeyEventHandler { +public: + KeyEventHandler(); + KeyEventHandler(const KeyEventHandler& orig); + virtual ~KeyEventHandler(); +private: + +}; + +#endif /* KEYEVENTHANDLER_H */ + diff --git a/src/KeyRepeatHandler.cpp b/src/KeyRepeatHandler.cpp new file mode 100644 index 0000000..858f5d5 --- /dev/null +++ b/src/KeyRepeatHandler.cpp @@ -0,0 +1,79 @@ +/* + epple2 + Copyright (C) 2022 by Christopher A. Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +/* + * File: KeyRepeatHandler.cpp + * Author: chris.mosher + * + * Created on December 19, 2022, 7:35 AM + */ + +#include "KeyRepeatHandler.h" +#include "e2const.h" + +// U.A.2 p. 7-13: REPT key repeats at 10Hz. +static const int CYCLES_PER_REPT(E2Const::AVG_CPU_HZ / 10); + +KeyRepeatHandler::KeyRepeatHandler(KeypressQueue &q) : + repeat(false), + is_key_down(false), + keypresses(q) { +} + +KeyRepeatHandler::~KeyRepeatHandler() { +} + +void KeyRepeatHandler::tick() { + if (this->repeat) { + // Count our way down to when the timer for the REPT key + // fires off: 10Hz in terms of how many CPU cycles have gone + // by + --this->rept; + // If it's time for the REPT key timer to fire (at long + // last)... + if (this->rept <= 0) { + // ...reload the timer for the next firing 1/10 second from + // now ( *reset* the timer ) + this->rept = CYCLES_PER_REPT; + // If any other keys are actually being held down... + if (this->is_key_down) { + // ...REPEAT the most recent one that was pressed + this->keypresses.push(this->key); + } + } + } +} + +void KeyRepeatHandler::setKey(unsigned char lastKeyDown) { + this->is_key_down = true; + this->key = lastKeyDown; +} + +void KeyRepeatHandler::clearKey() { + this->is_key_down = false; +} + +void KeyRepeatHandler::press() { + this->repeat = true; + this->rept = CYCLES_PER_REPT; +} + +void KeyRepeatHandler::release() { + this->repeat = false; + this->rept = 0; +} diff --git a/src/KeyRepeatHandler.h b/src/KeyRepeatHandler.h new file mode 100644 index 0000000..d42733f --- /dev/null +++ b/src/KeyRepeatHandler.h @@ -0,0 +1,52 @@ +/* + epple2 + Copyright (C) 2022 by Christopher A. Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +/* + * File: KeyRepeatHandler.h + * Author: chris.mosher + * + * Created on December 19, 2022, 7:35 AM + */ + +#ifndef KEYREPEATHANDLER_H +#define KEYREPEATHANDLER_H + +#include "keyboard.h" + +class KeyRepeatHandler { + bool repeat; + int rept; + bool is_key_down; + unsigned char key; + + KeypressQueue &keypresses; + +public: + KeyRepeatHandler(KeypressQueue &keypresses); + virtual ~KeyRepeatHandler(); + + virtual void tick(); + + void setKey(unsigned char lastKeyDown); + void clearKey(); + + void press(); + void release(); +}; + +#endif /* KEYREPEATHANDLER_H */ diff --git a/src/emulator.cpp b/src/emulator.cpp index 971b72d..21bbd2f 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -20,6 +20,7 @@ #include "e2config.h" #include "e2command.h" #include "e2const.h" +#include "KeyRepeatHandler.h" #include #include @@ -52,7 +53,7 @@ Emulator::Emulator() : videoStatic(display), apple2(keypresses, paddleButtonStates, display, buffered, screenImage), timable(nullptr), // No ticked object (NULL pointer) - repeat(false), + keyrepeater(keypresses), keysDown(0), prev_ms(SDL_GetTicks()) { } @@ -70,31 +71,6 @@ void Emulator::config(E2Config& cfg) { } -// 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() { - if (this->repeat) { - // Count our way down to when the timer for the REPT key - // fires off: 10Hz in terms of how many CPU cycles have gone - // by - --this->rept; - // If it's time for the REPT key timer to fire (at long - // last)... - if (this->rept <= 0) { - // ...reload the timer for the next firing 1/10 second from - // now ( *reset* the timer ) - this->rept = CYCLES_PER_REPT; - // If any other keys are actually being held down... - if (this->keysDown > 0) { - // ...REPEAT the most recent one that was pressed - this->keypresses.push(this->lastKeyDown); - } - } - } -} void Emulator::handleAnyPendingEvents() { SDL_Event event; @@ -134,10 +110,12 @@ void Emulator::tick50ms() { if (this->timable) { for (int i = 0; i < CHECK_EVERY_CYCLE; ++i) { this->timable->tick(); // this runs the emulator! - handleRepeatKey(); + this->keyrepeater.tick(); // TODO move into Apple2 } } + handleAnyPendingEvents(); + this->screenImage.displayHz((1000*CHECK_EVERY_CYCLE)/(SDL_GetTicks() - this->prev_ms)); this->prev_ms = SDL_GetTicks(); } @@ -225,7 +203,7 @@ static bool translateKeysToAppleModernized(SDL_Keycode keycode, SDL_Keymod modif // Take real-world keystrokes from SDL and filter them to emulate the Apple ][ keyboard void Emulator::dispatchKeyDown(const SDL_KeyboardEvent& keyEvent) { if (keyEvent.repeat) { - // To repeat on the real Apple ][, you need to use the REPT key (emulated below by F10) + // To repeat on the real Apple ][, you need to use the REPT key (emulated by F10) return; } @@ -239,18 +217,16 @@ void Emulator::dispatchKeyDown(const SDL_KeyboardEvent& keyEvent) { } if (sym == SDLK_F10) { - // handle REPT key - this->repeat = true; - this->rept = CYCLES_PER_REPT; - } else if (SDLK_F1 <= sym && sym <= SDLK_F12) { - wxGetApp().OnFnKeyPressed(sym); + this->keyrepeater.press(); +// } else if (SDLK_F1 <= sym && sym <= SDLK_F12) { +// wxGetApp().OnFnKeyPressed(sym); } else { unsigned char key; const bool sendKey = translateKeysToAppleModernized(sym, mod, &key); if (sendKey) { //printf(" sending to apple as ASCII ------------------------------> %02X (%02X) (%d)\n", key, key | 0x80, key | 0x80); this->keypresses.push(key); - this->lastKeyDown = key; + this->keyrepeater.setKey(key); } } } @@ -261,11 +237,13 @@ void Emulator::dispatchKeyUp(const SDL_KeyboardEvent& keyEvent) { if (isKeyDown(sym, mod)) { --this->keysDown; - } else if (sym == SDLK_F10) { - // ...else if this is the emulated REPT key on the Apple keyboard... - // ...stop repeating. The key has been released - this->repeat = false; - this->rept = 0; + if (this->keysDown <= 0) { + this->keyrepeater.clearKey(); + } + } + + if (sym == SDLK_F10) { + this->keyrepeater.release(); } } @@ -337,10 +315,11 @@ bool Emulator::isSafeToQuit() { void Emulator::toggleComputerPower() { - if (this->timable == &this->videoStatic) + if (this->timable == &this->videoStatic) { powerOnComputer(); - else + } else { powerOffComputer(); + } } void Emulator::powerOnComputer() { diff --git a/src/emulator.h b/src/emulator.h index a2eafd3..c3249bf 100644 --- a/src/emulator.h +++ b/src/emulator.h @@ -25,6 +25,7 @@ #include "screenimage.h" #include "analogtv.h" #include "keyboardbuffermode.h" +#include "KeyRepeatHandler.h" #include "clipboardhandler.h" #include #include @@ -35,6 +36,7 @@ class E2Config; class Emulator { PaddleButtonStates paddleButtonStates; KeypressQueue keypresses; + KeyRepeatHandler keyrepeater; KeyboardBufferMode buffered; ScreenImage screenImage; @@ -45,10 +47,7 @@ class Emulator { Timable* timable; - bool repeat; - int keysDown; - int rept; - unsigned char lastKeyDown; + int keysDown; // TODO move to KeyEventHandler Uint32 prev_ms; void powerOnComputer(); diff --git a/src/paddles.cpp b/src/paddles.cpp index 42d1260..b138c0b 100644 --- a/src/paddles.cpp +++ b/src/paddles.cpp @@ -14,79 +14,88 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . -*/ + */ #include "e2const.h" #include "paddles.h" + +#include +#include + #include + +#include + #include #include -Paddles::Paddles(): - rTick(PADDLE_COUNT) -{ + +Paddles::Paddles() : rTick(PADDLE_COUNT) { } - -Paddles::~Paddles() -{ +Paddles::~Paddles() { } - -void Paddles::tick() -{ - for (int paddle = 0; paddle < PADDLE_COUNT; ++paddle) - { - if (this->rTick[paddle] > 0) +void Paddles::tick() { + for (int paddle = 0; paddle < PADDLE_COUNT; ++paddle) { + if (this->rTick[paddle] > 0) { --this->rTick[paddle]; + } } } -void Paddles::startTimers() -{ - try - { +void Paddles::startTimers() { + try { tryStartPaddleTimers(); - } - catch (...) - { - std::cerr << "Warning: cannot start paddle timers; mouse will not function as paddles." << std::endl; + } catch (...) { + BOOST_LOG_TRIVIAL(error) << "Warning: cannot start paddle timers; mouse will not function as paddles."; } } -void Paddles::tryStartPaddleTimers() -{ +static wxPoint current_mouse_position() { int x, y; - SDL_GetMouseState(&x,&y); + SDL_GetMouseState(&x, &y); + return wxPoint(x, y); +} + +void Paddles::tryStartPaddleTimers() { + // wxWindow *pwin = ::wxGetActiveWindow(); + // const wxPoint w = pwin->GetPosition(); // crash + // BOOST_LOG_TRIVIAL(info) << "x: " << w.x << ", y: " << w.y; + // constwxPoint m = ::wxGetMousePosition(); + // const wxPoint p = w-m; + const wxPoint p = current_mouse_position(); double pMin = 0; double pMax = 500; - x = (int)((x-pMin)/(pMax-pMin)*PADDLE_CYCLES+.5); - y = (int)((y-pMin)/(pMax-pMin)*PADDLE_CYCLES+.5); + int x = (int) ((p.x - pMin) / (pMax - pMin) * PADDLE_CYCLES + .5); + int y = (int) ((p.y - pMin) / (pMax - pMin) * PADDLE_CYCLES + .5); - if (isTimedOut(0)) + if (isTimedOut(0)) { this->rTick[0] = x; - if (isTimedOut(1)) + } + if (isTimedOut(1)) { this->rTick[1] = y; + } /* Here we emulate having 4700 ohm across pins 7 and 1 - of the game controller, and a 47Kohm resistor acros + of the game controller, and a 47K ohm resistor across pins 11 and 1, to give cheap real-time clocks at paddles 2 and 3. Paddle 2 is the 100 microsecond reference, and paddle 3 is the 1 millisecond reference. This is described in U.A.2, p. 7-33. - */ - if (isTimedOut(2)) - this->rTick[2] = E2Const::AVG_CPU_HZ/10000; // was 90, but why? - if (isTimedOut(3)) - this->rTick[3] = E2Const::AVG_CPU_HZ/1000; + */ + if (isTimedOut(2)) { + this->rTick[2] = E2Const::AVG_CPU_HZ / 10000; // was 90, but why? + } + if (isTimedOut(3)) { + this->rTick[3] = E2Const::AVG_CPU_HZ / 1000; + } } -bool Paddles::isTimedOut(const int paddle) -{ - if (paddle < 0 || PADDLE_COUNT <= paddle) - { +bool Paddles::isTimedOut(const int paddle) { + if (paddle < 0 || PADDLE_COUNT <= paddle) { return false; } return this->rTick[paddle] <= 0; diff --git a/src/screenimage.cpp b/src/screenimage.cpp index 331b803..5fb3220 100644 --- a/src/screenimage.cpp +++ b/src/screenimage.cpp @@ -16,11 +16,19 @@ along with this program. If not, see . */ #include "screenimage.h" +#include "E2wxApp.h" +#include "E2wxFrame.h" #include "e2const.h" #include "applentsc.h" #include "card.h" #include "util.h" + +#include +#include +#include + #include + #include #include #include @@ -57,6 +65,8 @@ class ScreenException { }; ScreenImage::ScreenImage() : + wxFrame(nullptr/*wxGetApp().GetFrame()*/, wxID_ANY, "Emulator", wxDefaultPosition, wxDefaultSize, + wxSYSTEM_MENU | wxCLOSE_BOX | wxCAPTION | wxCLIP_CHILDREN), fullscreen(false), buffer(true), display(AnalogTV::TV_OLD_COLOR), @@ -64,12 +74,16 @@ ScreenImage::ScreenImage() : cassInName(32, ' '), cassOutName(32, ' ') { createScreen(); + Show(); } ScreenImage::~ScreenImage() { destroyScreen(); } +wxBEGIN_EVENT_TABLE(ScreenImage, wxFrame) +wxEND_EVENT_TABLE() + void ScreenImage::exitFullScreen() { if (this->fullscreen) { toggleFullScreen(); @@ -83,7 +97,30 @@ void ScreenImage::toggleFullScreen() { } void ScreenImage::createScreen() { - this->window = SDL_CreateWindow("Epple ][", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SCRW, SCRH*ASPECT_RATIO, SDL_WINDOW_OPENGL|SDL_WINDOW_SHOWN); + wxSizer *pszr = new wxBoxSizer(wxVERTICAL); + + this->panelTop = new wxPanel(this); + // TODO why won't shaped work? (Disabled frame resize as a workaround, for now.) + pszr->Add(this->panelTop, wxSizerFlags(0).Expand().Shaped().Center()); + + wxPanel *panelSdl = new wxPanel(this->panelTop, wxID_ANY, wxDefaultPosition, wxSize(SCRW,SCRH*ASPECT_RATIO)); + createSdlTexture(panelSdl); + + drawLabels(); + notifyObservers(); + + SetSizer(pszr); + pszr->SetSizeHints(this); + +} + +void ScreenImage::createSdlTexture(wxPanel *panelSdl) { + WXWidget nativeSdl = panelSdl->GetHandle(); + // TODO: do we need special gtk handling here, to get xid using: +// GtkWidget* widget = panel->GetHandle(); +// gtk_widget_realize(widget); +// Window xid = GDK_WINDOW_XWINDOW(widget->window); + this->window = SDL_CreateWindowFrom(static_cast(nativeSdl)); if (this->window == NULL) { printf("Unable to create window: %s\n", SDL_GetError()); throw ScreenException(); @@ -105,9 +142,6 @@ void ScreenImage::createScreen() { this->pixels = (unsigned int*) calloc(SCRW * SCRH, sizeof (unsigned int)); this->screen_pitch = SCRW; - - drawLabels(); - notifyObservers(); } void ScreenImage::destroyScreen() { diff --git a/src/screenimage.h b/src/screenimage.h index 8fd969a..b89f992 100644 --- a/src/screenimage.h +++ b/src/screenimage.h @@ -19,6 +19,10 @@ #define SCREENIMAGE_H #include "analogtv.h" + +#include +#include + #include #include #include @@ -28,8 +32,9 @@ struct SDL_Texture; struct SDL_Renderer; struct SDL_Window; -class ScreenImage { +class ScreenImage : public wxFrame { private: + wxPanel *panelTop; SDL_Window* window; SDL_Renderer* renderer; SDL_Texture* texture; @@ -39,6 +44,7 @@ private: bool buffer; AnalogTV::DisplayType display; void createScreen(); + void createSdlTexture(wxPanel *panelSdl); void destroyScreen(); std::vector slotnames; std::string cassInName; @@ -46,6 +52,8 @@ private: static std::string truncateFilePath(const std::filesystem::path& filepath); + wxDECLARE_EVENT_TABLE(); + // TODO some of these methods should be private public: ScreenImage();