Epple-II/src/screenimage.cpp

572 lines
16 KiB
C++

/*
epple2
Copyright (C) 2008 by Christopher A. Mosher <cmosher01@gmail.com>
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 <http://www.gnu.org/licenses/>.
*/
#include "screenimage.h"
#include "E2wxApp.h"
#include "E2wxFrame.h"
#include "e2const.h"
#include "applentsc.h"
#include "card.h"
#include "gtkutil.h"
#include "util.h"
#include "emulator.h"
#include <wx/menu.h>
#include <wx/panel.h>
#include <wx/sizer.h>
#include <wx/gdicmn.h>
#include <SDL.h>
#include <filesystem>
#include <iostream>
#include <ctime>
#include <sstream>
#include <cstdio>
#include <cstring>
// TODO For screen image in general, remove items from this SDL window to the wx frame as appropriate
static const char* power =
" @@@@ @@@ @ @ @ @@@@@ @@@@ "
" @ @ @ @ @ @ @ @ @ @ @"
" @ @ @ @ @ @ @ @ @ @ @"
" @@@@ @ @ @ @ @ @ @@@@@ @@@@ "
" @ @ @ @ @ @ @ @ @ @ "
" @ @ @ @ @ @ @ @ @ @ "
" @ @@@ @ @ @@@@@ @ @";
#define POWERD 56
#define LABEL_Y 20
#define ON_CLR 0xF0D050
#define OFF_CLR 0x807870
#define SCRW 640
#define SCRH 480
#define ASPECT_RATIO 1.191 /* UA2, p. 8-28 */
#define R_SLOT 67
static const int HEIGHT = E2Const::VISIBLE_ROWS_PER_FIELD * 2;
static const int WIDTH = AppleNTSC::H - AppleNTSC::PIC_START - 2;
#include "font3x5.h"
class ScreenException {
};
ScreenImage::ScreenImage(Emulator &emulator, KeyEventHandler &k) :
wxFrame(nullptr, wxID_ANY, "Emulator"),
emu(emulator),
fullscreen(false),
buffer(true),
display(AnalogTV::TV_OLD_COLOR),
slotnames(8),
cassInName(32, ' '),
cassOutName(32, ' '),
keyEventHandler(k) {
createScreen();
Center();
Show();
Bind(wxEVT_IDLE, &ScreenImage::OnIdle, this);
Bind(wxEVT_CLOSE_WINDOW, &ScreenImage::HandleUserCloseWindowRequest, this);
}
ScreenImage::~ScreenImage() {
destroyScreen();
}
void ScreenImage::OnIdle(wxIdleEvent &evt) {
if (!this->FindFocus() || !this->sdl->HasFocus()) {
this->sdl->SetFocus();
}
}
void ScreenImage::HandleUserCloseWindowRequest(wxCloseEvent& event) {
wxGetApp().StopEmulator();
}
void ScreenImage::exitFullScreen() {
// if (this->fullscreen) {
// toggleFullScreen();
// }
}
void ScreenImage::toggleFullScreen() {
// this->fullscreen = !this->fullscreen;
// const int flags = this->fullscreen ? SDL_WINDOW_FULLSCREEN : 0;
// SDL_SetWindowFullscreen(this->window, flags);
}
void ScreenImage::createScreen() {
this->sdl = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(SCRW,SCRH*ASPECT_RATIO));
createSdlTexture();
this->sdl->Bind(wxEVT_KEY_DOWN, &ScreenImage::OnKeyDown, this);
this->sdl->Bind(wxEVT_KEY_UP, &ScreenImage::OnKeyUp, this);
wxSizer *pszr = new wxBoxSizer(wxVERTICAL);
pszr->Add(this->sdl);
SetSizer(pszr);
pszr->SetSizeHints(this);
this->pixels = (unsigned int*) calloc(SCRW * SCRH, sizeof (unsigned int));
this->screen_pitch = SCRW;
drawLabels();
notifyObservers();
}
static void *get_native_window_handle(wxWindowBase *panel) {
void *vn = static_cast<void*>(panel->GetHandle());
#ifdef __WXGTK__
vn = get_gtk_native_window_handle(vn);
#endif
return vn;
}
void ScreenImage::createSdlTexture() {
void *nativeSdl = get_native_window_handle(this->sdl);
this->window = SDL_CreateWindowFrom(nativeSdl);
if (this->window == NULL) {
printf("Unable to create window: %s\n", SDL_GetError());
throw ScreenException();
}
SDL_SetWindowSize(this->window, SCRW, SCRH*ASPECT_RATIO);
this->renderer = SDL_CreateRenderer(this->window, -1, 0);
if (this->renderer == NULL) {
std::cerr << SDL_GetError() << std::endl;
throw ScreenException();
}
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear");
this->texture = SDL_CreateTexture(this->renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, SCRW, SCRH);
if (this->texture == NULL) {
std::cerr << SDL_GetError() << std::endl;
throw ScreenException();
}
}
void ScreenImage::destroyScreen() {
SDL_DestroyTexture(this->texture);
SDL_DestroyRenderer(this->renderer);
SDL_DestroyWindow(this->window);
}
void ScreenImage::drawLabels() {
drawText("EPPLE ][", 0, 141);
drawText("ANNUNCIATORS: 0: 1: 2: 3:", 65, 17);
drawCassette();
drawSlots();
drawFnKeys();
}
void ScreenImage::drawSlots() {
int r(R_SLOT-1);
int c(17);
drawText("SLOTS:", r++, c);
for (int slot(0); slot < 8; ++slot) {
drawSlot(slot, r++, c);
}
}
void ScreenImage::drawSlot(int slot, int r, int c) {
drawChar('0' + slot, r, c++);
drawChar(':', r, c++);
drawChar(' ', r, c++);
drawText(this->slotnames[slot], r, c);
const int len = this->slotnames[slot].length();
if (len < 100) {
drawText(std::string(100 - len, ' '), r, c + len);
}
}
void ScreenImage::drawCassette() {
int r(65);
int c(80);
drawText("CASSETTE: IN<-", r, c);
c += 15;
drawText(this->cassInName, r, c);
int len = this->cassInName.length();
if (len < 40) {
drawText(std::string(40 - len, ' '), r, c + len);
}
++r;
c = 81+9;
drawText("OUT->", r, c);
c += 5;
drawText(this->cassOutName, r, c);
len = this->cassOutName.length();
if (len < 40) {
drawText(std::string(40 - len, ' '), r, c + len);
}
}
static const char* displays[] = {
"TELEVISION ",
"COLOR MONITOR ",
"GREEN MONITOR ",
};
void ScreenImage::drawFnKeys() {
int r(76);
int c(1);
drawText(
" FULLSCRN KEYBOARD", r++, c);
drawText(
" XXXXXXXXXXXXXX WINDOW CMD RESET PASTE SAVE BMP QUIT! REPT BUFFER ", r++, c);
drawText(
" F1 F2 F3 F5 F6 F7 F8 F9 F10 F12 ", r++, c);
if (this->fullscreen)
invertText(76, 32, 42); // FULLSCRN
else
invertText(77, 32, 40); // WINDOW
if (this->buffer)
invertText(77, 110, 118); // BUFFER
drawDisplayLabel();
}
void ScreenImage::drawDisplayLabel() {
const char* label = displays[(int) (this->display)];
drawText(label, 77, 17);
}
void ScreenImage::cycleDisplayLabel() {
this->display = (AnalogTV::DisplayType)((((int) this->display) + 1) % AnalogTV::NUM_DISPLAY_TYPES);
drawDisplayLabel();
}
void ScreenImage::toggleKdbBufferLabel() {
this->buffer = !this->buffer;
invertText(77, 110, 118); // BUFFER
}
void ScreenImage::invertText(int row, int begincol, int endcol) {
unsigned int* pn = this->pixels;
pn += row * FONTH * SCRW + begincol*FONTW;
const int dc = (endcol - begincol) * FONTW;
for (int ir = 0; ir < FONTH; ++ir) {
for (int ic = 0; ic < dc; ++ic) {
*pn = ~*pn;
++pn;
}
pn -= dc;
pn += SCRW;
}
}
void ScreenImage::drawText(const std::string& text, int row, int col, int color, int bgcolor) {
for (std::string::const_iterator i = text.begin(); i != text.end(); ++i) {
char c = (*i) & 0x7F;
if (0 <= c && c < 0x20)
c += 0x40;
drawChar(c, row, col++, color, bgcolor);
}
}
void ScreenImage::drawChar(const char ch, int row, int col, int color, int bgcolor) {
if (!(0 <= row && row < SCRH / 6 && 0 <= col && col < SCRW / 4)) {
printf("bad text plotting (r %d, c %d): \"%c\"\n", row, col, ch);
}
unsigned int* pn = this->pixels;
pn += row * FONTH * SCRW + col*FONTW;
const char* pt = font3x5 + FONTH * FONTW * (ch - 0x20);
for (int r = 0; r < FONTH; ++r) {
for (int c = 0; c < FONTW; ++c) {
*pn++ = *pt++ == '@' ? color : bgcolor;
}
pn -= FONTW;
pn += SCRW;
}
}
void ScreenImage::displayHz(int hz) {
char s[20];
sprintf(s, "%5.3f MHz ", hz / 1e6);
drawText(s, 3, 141);
}
void ScreenImage::drawPower(bool on) {
unsigned int* pn = this->pixels;
pn += (HEIGHT + 35)*SCRW + 5;
for (int r = 0; r < POWERD/ASPECT_RATIO; ++r) {
if (r < LABEL_Y || LABEL_Y + 7 <= r) {
for (int c = 0; c < POWERD; ++c) {
*pn++ = on ? ON_CLR : OFF_CLR;
}
} else {
{
for (int c = 0; c < 8; ++c) {
*pn++ = on ? ON_CLR : OFF_CLR;
}
}
for (const char* ppow = power + (r - (LABEL_Y))*40; ppow < power + (r - (LABEL_Y - 1))*40; ++ppow) {
if (*ppow == '@')
*pn++ = 0;
else
*pn++ = on ? ON_CLR : OFF_CLR;
}
{
for (int c = 0; c < 8; ++c) {
*pn++ = on ? ON_CLR : OFF_CLR;
}
}
}
pn -= POWERD;
pn += SCRW;
}
notifyObservers();
}
void ScreenImage::notifyObservers() {
const int e = SDL_UpdateTexture(this->texture, NULL, this->pixels, SCRW*sizeof(unsigned int));
if (e) {
std::cerr << SDL_GetError() << std::endl;
}
SDL_RenderClear(this->renderer);
SDL_RenderCopy(this->renderer, this->texture, NULL, NULL);
SDL_RenderPresent(this->renderer);
SDL_RenderSetLogicalSize(this->renderer, SCRW, SCRH*ASPECT_RATIO);
}
void ScreenImage::setElem(unsigned int i, const unsigned int val) {
unsigned int* pn = this->pixels;
i += (i/WIDTH)*(SCRW-WIDTH);
pn += i;
*pn = val;
}
void ScreenImage::blank() {
for (int r = 0; r < HEIGHT; ++r) {
memset((char*)(this->pixels)+r*SCRW*4, 0, WIDTH*4);
}
}
void ScreenImage::updateSlotName(const int slot, Card* card) {
int r(R_SLOT + slot);
int c(20);
const std::string& name = card->getName();
this->slotnames[slot] = name;
drawText(std::string(80, ' '), r, c);
drawText(name, r, c);
}
void ScreenImage::removeCard(const int slot, Card* card /* empty */) {
updateSlotName(slot, card);
}
/*
1 2 3 4 5 6 7 8
789012345678901234567890123456789012345678901234567890123456789012345
6: disk][ drive 1M*filename.nib T$FF drive 2M*filename.nib T$FF
*/
void ScreenImage::setDiskFile(int slot, int drive, const std::filesystem::path &filepath) {
std::string f = truncateFilePath(filepath);
int r(R_SLOT + slot);
int c(37 + 32 * drive);
drawText(f, r, c);
const int dlen = 12 - f.length();
if (dlen > 0) {
std::string d(dlen, ' ');
drawText(d, r, c + f.length());
}
this->slotnames[slot].replace(c - 20, 12, 12, ' ');
this->slotnames[slot].replace(c - 20, f.length(), f);
}
std::string ScreenImage::truncateFilePath(const std::filesystem::path& filepath) {
return filepath.stem().string().substr(0, 12);
}
void ScreenImage::clearCurrentDrive(int slot, int drive) {
int r(R_SLOT + slot);
int c(35 + 32 * drive);
drawChar(' ', r, c);
this->slotnames[slot][c - 20] = ' ';
c += 15;
drawText(" ", r, c);
this->slotnames[slot].replace(c - 20, 4, 4, ' ');
}
void ScreenImage::setCurrentDrive(int slot, int drive, int track, bool on) {
int r(R_SLOT + slot);
int c(35 + 32 * drive);
drawChar(' ', r, c, 0xFFFFFF, on ? 0xFF0000 : 0);
c += 15;
drawChar('T', r, c);
this->slotnames[slot][c - 20] = 'T';
++c;
drawChar('$', r, c);
this->slotnames[slot][c - 20] = '$';
++c;
char nibh = Util::hexDigit((((unsigned char) track) >> 4) & 0xF);
drawChar(nibh, r, c);
this->slotnames[slot][c - 20] = nibh;
++c;
char nibl = Util::hexDigit((unsigned char) track & 0xF);
drawChar(nibl, r, c);
this->slotnames[slot][c - 20] = nibl;
}
void ScreenImage::setTrack(int slot, int drive, int track) {
int r(R_SLOT + slot);
int c(52 + 32 * drive);
char nibh = Util::hexDigit((((unsigned char) track) >> 4) & 0xF);
drawChar(nibh, r, c);
this->slotnames[slot][c - 20] = nibh;
++c;
char nibl = Util::hexDigit((unsigned char) track & 0xF);
drawChar(nibl, r, c);
this->slotnames[slot][c - 20] = nibl;
}
void ScreenImage::setAnnunciator(int ann, bool on) {
drawChar(' ', 65, 33+4*ann, 0xFFFFFF, on ? 0xFF0000 : 0);
}
void ScreenImage::setIO(int slot, int drive, bool on) {
int r(R_SLOT + slot);
int c(35 + 32 * drive);
drawChar(' ', r, c, 0xFFFFFF, on ? 0xFF0000 : 0);
}
void ScreenImage::setDirty(int slot, int drive, bool dirty) {
int r(R_SLOT + slot);
int c(36 + 32 * drive);
drawChar(dirty ? '*' : ' ', r, c);
this->slotnames[slot][c - 20] = dirty ? '*' : ' ';
}
void ScreenImage::setCassetteInFile(const std::filesystem::path& filepath) {
std::string f = truncateFilePath(filepath);
int r(65);
int c(85 + 11);
drawText(f, r, c);
const int dlen = 12 - f.length();
if (dlen > 0) {
std::string d(dlen, ' ');
drawText(d, r, c + f.length());
}
this->cassInName.replace(c - 94, 12, 12, ' ');
this->cassInName.replace(c - 94, f.length(), f);
}
void ScreenImage::setCassetteOutFile(const std::filesystem::path& filepath) {
std::string f = truncateFilePath(filepath);
int r(66);
int c(85 + 11);
drawText(f, r, c);
const int dlen = 12 - f.length();
if (dlen > 0) {
std::string d(dlen, ' ');
drawText(d, r, c + f.length());
}
this->cassOutName.replace(c - 94, 12, 12, ' ');
this->cassOutName.replace(c - 94, f.length(), f);
}
void ScreenImage::setCassetteDirty(bool dirty) {
int r(66);
int c(85 + 10);
drawChar(dirty ? '*' : ' ', r, c);
this->cassOutName[c - 94] = dirty ? '*' : ' ';
}
void ScreenImage::setCassettePos(unsigned int pos, unsigned int siz) {
int r(65);
int c(110);
std::ostringstream os;
os << pos << '/' << siz << " ";
drawText(os.str(), r, c);
}
/*
1 2 3 4 5 6 7 8
789012345678901234567890123456789012345678901234567890123456789012345678
0: language RW B2
*/
void ScreenImage::setLangCard(int slot, bool readEnable, bool writeEnable, int bank) {
int r(R_SLOT + slot);
int c(29);
drawChar(readEnable ? 'R' : ' ', r, c);
this->slotnames[slot][c - 20] = readEnable ? 'R' : ' ';
++c;
drawChar(writeEnable ? 'W' : ' ', r, c);
this->slotnames[slot][c - 20] = writeEnable ? 'W' : ' ';
++c;
++c;
drawChar('B', r, c);
this->slotnames[slot][c - 20] = 'B';
++c;
drawChar(bank == 0 ? '1' : '2', r, c);
this->slotnames[slot][c - 20] = bank == 0 ? '1' : '2';
}
/*
1 2 3 4 5 6 7 8
789012345678901234567890123456789012345678901234567890123456789012345678
0: firmware D F8
*/
void ScreenImage::setFirmCard(int slot, bool bank, bool F8) {
int r(R_SLOT + slot);
int c(29);
drawChar(bank ? 'D' : ' ', r, c);
this->slotnames[slot][c - 20] = bank ? 'D' : ' ';
++c;
++c;
drawChar(F8 ? 'F' : ' ', r, c);
this->slotnames[slot][c - 20] = F8 ? 'F' : ' ';
++c;
drawChar(F8 ? '8' : ' ', r, c);
this->slotnames[slot][c - 20] = F8 ? '8' : ' ';
}
#define TIMEFORMAT "ep2_%Y%m%d%H%M%S.bmp"
void ScreenImage::saveBMP() {
time_t now;
::time(&now);
struct tm* nowtm = ::localtime(&now);
char time[64];
::strftime(time, sizeof (time), TIMEFORMAT, nowtm);
SDL_Surface* screenshot = SDL_CreateRGBSurfaceFrom(this->pixels,SCRW,SCRH,32,SCRW*4,0x00FF0000,0x0000FF00,0x000000FF,0xFF000000);
SDL_SaveBMP(screenshot, time);
SDL_FreeSurface(screenshot);
}
void ScreenImage::OnKeyDown(wxKeyEvent &evt) {
this->keyEventHandler.dispatchKeyDown(evt);
}
void ScreenImage::OnKeyUp(wxKeyEvent &evt) {
this->keyEventHandler.dispatchKeyUp(evt);
}