Compare commits

...

8 Commits

Author SHA1 Message Date
Christopher A. Mosher 7552b0ce11 move test out of main and add a cmd line option to run them 2022-12-12 02:12:17 -05:00
Christopher A. Mosher 6a52aadfe4 handle quit/close events across SDL and wx sides including asking to save changes; refactor to remove old unused event loop code 2022-12-12 00:52:19 -05:00
Christopher A. Mosher 49d6e664f5 fix truncateFilePath 2022-12-11 23:08:04 -05:00
Christopher A. Mosher dde4bd09a7 fix path reference 2022-12-11 22:27:37 -05:00
Christopher A. Mosher 46582c6566 refactor: split commands out of config, clean up some parsing, add media concept to card abstraction 2022-12-11 22:07:50 -05:00
Christopher A. Mosher b138df21eb rename Config class to E2Config; remove an unused function 2022-12-11 20:13:39 -05:00
Christopher A. Mosher f0923c00dd fix windows build 2022-12-11 19:41:04 -05:00
Christopher A. Mosher fee69a814c completely remove tinyfiledialogs; clean up some log messages 2022-12-11 17:17:25 -05:00
26 changed files with 705 additions and 8647 deletions

View File

@ -66,7 +66,6 @@ Circuit.cpp
clipboardhandler.cpp
clockcard.cpp
Common.cpp
configep2.cpp
Cpu6502.cpp
Cpu6502Helper.cpp
cpu.cpp
@ -77,6 +76,8 @@ disk2steppermotorrotor.cpp
diskcontroller.cpp
drive.cpp
drivemotor.cpp
e2config.cpp
e2command.cpp
e2filesystem.cpp
e2string.cpp
E2wxApp.cpp
@ -116,7 +117,6 @@ standardout.cpp
StateCalculator.cpp
textcharacters.cpp
timable.cpp
tinyfiledialogs.cpp
Trace.cpp
TransCache.cpp
TransNetwork.cpp

View File

@ -10,7 +10,7 @@ Copyright © 20082022, Christopher Alan Mosher, Shelton, Connecticut, USA, <c
See file [`LICENSE.other`](LICENSE.other) for additional copyright notices.
Please note this project is released with a Contributor Code of Conduct. By
Please note this project is released with a Contributor Code of Conduct. By
participating in this project you agree to abide by its terms.
---

View File

@ -23,12 +23,17 @@
#include "E2wxFrame.h"
#include "emulator.h"
#include "gui.h"
#include "configep2.h"
#include "e2config.h"
#include "e2filesystem.h"
#include "e2const.h"
#include <wx/app.h>
#include <wx/xrc/xmlres.h>
#include <wx/fileconf.h>
#include <wx/log.h>
#include <wx/stdpaths.h>
#include <wx/debugrpt.h>
#include <boost/log/core.hpp>
#include <boost/log/trivial.hpp>
#include <boost/log/utility/setup/file.hpp>
@ -38,7 +43,7 @@
#include <boost/log/expressions/formatters/stream.hpp>
#include <boost/log/expressions.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <wx/debugrpt.h>
#include <iostream>
#include <string>
#include <memory>
@ -70,8 +75,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 +89,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() {
@ -105,10 +114,6 @@ static std::filesystem::path dirDocuments() {
return path_from_string(wxStandardPaths::Get().GetAppDocumentsDir());
}
static std::filesystem::path dirResources() {
return path_from_string(wxStandardPaths::Get().GetResourcesDir());
}
@ -136,6 +141,14 @@ bool E2wxApp::OnInit() {
InitBoostLog();
// TODO investigate redirecting wxLogs to boost
// wxLog* logger = new wxLogStream(&std::cerr);
// wxLog::SetActiveTarget(logger);
// wxLogWarning("%s", "a warning has occurred");
// wxLogInfo("%s", "informational");
// wxLogVerbose("%s", "verbose");
// wxFile().Open("foobar.txt");
BOOST_LOG_TRIVIAL(info) << "Application ID: " << this->GetID();
BOOST_LOG_TRIVIAL(info) << "Application version: " << this->GetVersion();
@ -152,7 +165,7 @@ bool E2wxApp::OnInit() {
BOOST_LOG_TRIVIAL(info) << "User document directory path: " << this->docsdir;
const std::filesystem::path exe = path_from_string(stdpaths.GetExecutablePath());
std::cout << "Executable file path: " << exe << std::endl;
BOOST_LOG_TRIVIAL(info) << "Executable file path: " << exe;
std::filesystem::path res = exe.parent_path();
if (res.filename() == "bin" || res.filename() == "MacOS") {
res = res.parent_path();
@ -164,7 +177,7 @@ bool E2wxApp::OnInit() {
res /= "Resources";
}
this->resdir = res;
std::cout << "Resource directory path: " << this->resdir.c_str() << std::endl;
BOOST_LOG_TRIVIAL(info) << "Resource directory path: " << this->resdir;
wxXmlResource::Get()->InitAllHandlers();
if (!wxXmlResource::Get()->LoadAllFiles(this->resdir.c_str())) {
@ -173,18 +186,13 @@ bool E2wxApp::OnInit() {
E2wxFrame *frame = new E2wxFrame();
frame = new E2wxFrame();
frame->DoInit();
frame->Show();
this->emu = new Emulator();
Config 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->begin();
StartEmulator();
@ -193,12 +201,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;
}
@ -219,6 +241,7 @@ void E2wxApp::OnFatalException() {
static const wxCmdLineEntryDesc cmdLineDesc[] = {
{ wxCMD_LINE_SWITCH, "p", "prefs", "Read config only from preferences, never an external file.", wxCMD_LINE_VAL_NONE },
{ wxCMD_LINE_SWITCH, "t", "test", "Run internal tests.", wxCMD_LINE_VAL_NONE },
{ wxCMD_LINE_PARAM, NULL, NULL, "config-file", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL },
wxCMD_LINE_DESC_END
};
@ -233,15 +256,25 @@ bool E2wxApp::OnCmdLineParsed(wxCmdLineParser& parser) {
return false;
}
if (parser.Found("t")) {
const int x = E2Const::test();
if (x != -1) {
std::cerr << x << std::endl;
throw std::runtime_error("bad constant in e2const.h" );
}
std::cerr << "pass" << std::endl;
return false;
}
this->opt_config_from_prefs_only = parser.Found("p");
const int n = parser.GetParamCount();
if (n <= 0) {
std::cout << "no config file specified on the command line; will use config file specified in user-preferences" << std::endl;
} else {
if (n == 1) {
this->arg_configfile = path_from_string(parser.GetParam(0));
std::cout << "using config file specified on the command line: " << this->arg_configfile.c_str() << std::endl;
} else if (1 < n) {
// should never happen, anyway
return false;
}
return true;
@ -297,7 +330,7 @@ const std::filesystem::path E2wxApp::BuildLogFilePath() const {
void E2wxApp::InitBoostLog() {
this->logfile = BuildLogFilePath();
std::cout << "log file: " << this->logfile << std::endl;
std::cerr << "log file: " << this->logfile << std::endl;
@ -313,3 +346,30 @@ void E2wxApp::InitBoostLog() {
boost::log::add_common_attributes();
}
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};
this->emu->config(cfg);
this->emu->init();
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,11 +61,13 @@ 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;
const std::filesystem::path BuildLogFilePath() const;
void InitBoostLog();
void StartEmulator();
public:
E2wxApp();
@ -78,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

@ -14,96 +14,89 @@
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 "card.h"
#include "configep2.h"
Card::Card():
rom(0x0100),
seventhRom(0x0800)
{
Card::Card() : rom(0x0100), seventhRom(0x0800) {
}
Card::~Card() {
}
Card::~Card()
{
std::string Card::getName() {
return "";
}
void Card::reset()
{
void Card::tick() {
}
void Card::tick()
{
void Card::reset() {
}
unsigned char Card::io(const unsigned short /*address*/, const unsigned char data, const bool /*writing*/)
{
unsigned char Card::io(const unsigned short address, const unsigned char data, const bool writing) {
return data;
}
unsigned char Card::readRom(const unsigned short address, const unsigned char data)
{
unsigned char Card::readRom(const unsigned short address, const unsigned char data) {
this->activeSeventhRom = true;
return this->rom.read(address, data);
}
void Card::readSeventhRom(const unsigned short address, unsigned char* const pb)
{
if (address == 0x7FF)
{
bool Card::hasSeventhRom() {
return false;
}
void Card::readSeventhRom(const unsigned short address, unsigned char* const pb) {
if (address == 0x7FF) {
this->activeSeventhRom = false;
}
else if (this->activeSeventhRom && hasSeventhRom())
{
} else if (this->activeSeventhRom && hasSeventhRom()) {
*pb = this->seventhRom.read(address, *pb);
}
}
void Card::loadRom(const unsigned short base, std::istream& in)
{
this->rom.load(base,in);
void Card::loadRom(const unsigned short base, std::istream& in) {
this->rom.load(base, in);
}
void Card::loadSeventhRom(const unsigned short base, std::istream& in)
{
this->seventhRom.load(base,in);
void Card::loadSeventhRom(const unsigned short base, std::istream& in) {
this->seventhRom.load(base, in);
}
bool Card::inhibitMotherboardRom()
{
bool Card::inhibitMotherboardRom() {
return false;
}
void Card::ioBankRom(const unsigned short addr, unsigned char* const pb, const bool write) {
}
void Card::ioBankRom(const unsigned short /*addr*/, unsigned char* const /*pb*/, const bool /*write*/)
{
void Card::loadBankRom(const unsigned short base, std::istream& in) {
// TODO? maybe just do nothing
// throw ConfigException("This card has no $D000 ROM");
}
void Card::loadBankRom(const unsigned short /*base*/, std::istream& /*in*/)
{
throw ConfigException("This card has no $D000 ROM");
}
std::string Card::getName()
{
return "";
}
bool Card::isDirty()
{
bool Card::isMediaDirty() {
return false;
}
void Card::save(int unit)
{
bool Card::hasMedia() {
return false;
}
void Card::loadMedia(int unit, const std::filesystem::path &media) {
}
void Card::unloadMedia(int unit) {
}
void Card::saveMedia(int unit) {
}

View File

@ -14,19 +14,19 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
*/
#ifndef CARD_H
#define CARD_H
#include "memory.h"
#include <filesystem>
#include <istream>
#include <string>
class Card
{
private:
class Card {
bool activeSeventhRom;
protected:
Memory rom;
Memory seventhRom;
@ -34,20 +34,27 @@ protected:
public:
Card();
virtual ~Card();
virtual void tick();
virtual void reset();
virtual std::string getName();
virtual void tick();
virtual void reset();
virtual unsigned char io(const unsigned short address, const unsigned char data, const bool writing);
virtual unsigned char readRom(const unsigned short address, const unsigned char data);
virtual bool hasSeventhRom() { return false; }
virtual bool hasSeventhRom();
virtual void readSeventhRom(const unsigned short address, unsigned char* const pb);
virtual void loadRom(const unsigned short base, std::istream& in);
virtual void loadSeventhRom(const unsigned short base, std::istream& in);
virtual bool inhibitMotherboardRom();
virtual void ioBankRom(const unsigned short addr, unsigned char* const pb, const bool write);
virtual void loadBankRom(const unsigned short base, std::istream& in);
virtual bool isDirty();
virtual void save(int unit);
virtual std::string getName();
virtual bool isMediaDirty();
virtual bool hasMedia();
virtual void loadMedia(int unit, const std::filesystem::path &media);
virtual void unloadMedia(int unit);
virtual void saveMedia(int unit);
};
#endif

View File

@ -16,12 +16,16 @@
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 "cassette.h"
#include "e2const.h"
#include <wx/msgdlg.h>
#include <fstream>
#include <iostream>
#include <cstdlib>
#include "tinyfiledialogs.h"
#include "cassette.h"
#include "e2const.h"
Cassette::Cassette(ScreenImage& gui):
gui(gui),
@ -56,25 +60,25 @@ void Cassette::tick() {
// 0=cancel(abort), 1=yes(save), 2=no(discard)
static int askSave() {
return tinyfd_messageBox(
"Save changes",
wxMessageDialog *dlg = new wxMessageDialog{
nullptr,
"You have unsaved changes to your tape image.\nDo you want to SAVE them?",
"yesnocancel",
"warning",
0);
"Save changes",
(long)(wxYES_NO|wxCANCEL|wxCANCEL_DEFAULT)};
return dlg->ShowModal();
}
bool Cassette::eject() {
if (isLoaded()) {
if (isModified()) {
const int resp = askSave();
if (resp == 0) { // cancel
if (resp == wxID_CANCEL) {
return false;
}
if (resp == 1) { // yes (save)
if (resp == wxID_YES) {
if (!write()) {
return false;
}
@ -97,6 +101,8 @@ void Cassette::save() {
if (write()) {
this->modified = false;
this->gui.setCassetteDirty(false);
} else {
// TODO show error messsage
}
}
}

View File

@ -21,6 +21,7 @@
#include "wozfile.h"
#include "lss.h"
#include "screenimage.h"
#include <filesystem>
#include <string>
#include <iostream>
#include <cstdint>
@ -94,21 +95,25 @@ public:
this->gui.setCurrentDrive(this->slot,getCurrentDriveNumber(),getTrack(),false);
}
void loadDisk(unsigned char drive, const std::string& fnib) {
if (!this->getDrive(drive).loadDisk(fnib)) {
virtual bool hasMedia() override {
return true;
}
virtual void loadMedia(int unit, const std::filesystem::path &media) override {
if (!this->getDrive(unit).loadDisk(media)) {
return;
}
this->gui.setDiskFile(this->slot,drive,fnib);
this->gui.setDiskFile(this->slot,unit,media);
this->gui.setDirty(this->slot,getCurrentDriveNumber(),false);
}
void unloadDisk(unsigned char drive) {
this->getDrive(drive).unloadDisk();
this->gui.setDiskFile(this->slot,drive,"");
virtual void unloadMedia(int unit) override {
this->getDrive(unit).unloadDisk();
this->gui.setDiskFile(this->slot,unit,std::filesystem::path{});
this->gui.setDirty(this->slot,getCurrentDriveNumber(),false);
}
void save(int drive) {
virtual void saveMedia(int drive) override {
this->getDrive(drive).saveDisk();
this->gui.setDirty(this->slot,getCurrentDriveNumber(),false);
}
@ -137,7 +142,7 @@ public:
return this->currentDrive->isWriteProtected();
}
bool isDirty() {
bool isMediaDirty() {
return isModified() || isModifiedOther();
}

View File

@ -16,6 +16,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "drive.h"
Disk2Drive::Disk2Drive(double p_random_ones_rate):
stepper(head),
pulse(false),
@ -25,7 +26,7 @@ Disk2Drive::Disk2Drive(double p_random_ones_rate):
distribution(0.0,1.0) {
}
bool Disk2Drive::loadDisk(const std::string& fnib) {
bool Disk2Drive::loadDisk(const std::filesystem::path& fnib) {
return this->disk.load(fnib);
}

View File

@ -18,6 +18,7 @@
#ifndef DRIVE_H
#define DRIVE_H
#include <filesystem>
#include <random>
#include <chrono>
#include <string>
@ -48,7 +49,7 @@ private:
public:
Disk2Drive(double p_random_ones_rate);
bool loadDisk(const std::string& fnib);
bool loadDisk(const std::filesystem::path& fnib);
void unloadDisk();
bool isLoaded() const;
void saveDisk();

View File

@ -15,13 +15,25 @@
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 "configep2.h"
/*
* File: e2command.cpp
* Author: user
*
* Created on December 11, 2022, 8:21 PM
*/
#include "e2command.h"
#include "E2wxApp.h"
#include "e2filesystem.h"
#include "apple2.h"
#include "memory.h"
#include "memoryrandomaccess.h"
#include "memorychip.h"
#include "cassette.h"
#include "cassettein.h"
#include "cassetteout.h"
#include "card.h"
#include "slots.h"
#include "diskcontroller.h"
#include "languagecard.h"
@ -29,28 +41,15 @@
#include "standardout.h"
#include "standardin.h"
#include "clockcard.h"
#include "cassettein.h"
#include "cassetteout.h"
#include "tinyfiledialogs.h"
#include "screenimage.h"
#include <wx/filedlg.h>
#include <wx/config.h>
#include <wx/string.h>
#include <boost/log/trivial.hpp>
#include <iostream>
#include <istream>
#include <fstream>
#include <sstream>
#include <filesystem>
#include <iostream>
#include <fstream>
#include <string>
#include <stdexcept>
#include <cctype>
static const wxString DEFAULT_CONFIG_NAME{"epple2"};
@ -66,262 +65,9 @@ static std::uint16_t memory_block_size(const std::string &block_size) {
throw ConfigException("invalid RAM strapping block size (must be 4K or 16K)");
}
Config::Config(const std::filesystem::path& f, bool p): file_path {f}, prefs_only {p} {
}
Config::~Config() {
}
static void strip_comment(std::string& str)
{
const size_t comment = str.find('#');
if (comment < std::string::npos)
{
str.erase(comment);
}
}
static void trim(std::string& str)
{
{
const size_t p = str.find_first_not_of(" \t");
if (p < std::string::npos)
{
str.erase(0,p);
}
}
{
const size_t p = str.find_last_not_of(" \t");
if (p+1 < std::string::npos)
{
str.erase(p+1);
}
}
}
/*
* Searches for config file with the given name in the preferences areas.
* The name must not be a filesystem path.
* The actual config files themselves have ".conf" extension.
* The preferences areas are searched in this order:
* user area
* built-in area
*
* If successful, returns an open stream, caller is responsible for deleting it
* Otherwise, returns null
*/
std::ifstream *Config::openFilePref(const wxString& s_name) {
std::ifstream *ret = nullptr;
BOOST_LOG_TRIVIAL(info) << "config file name was specified as: " << s_name;
std::filesystem::path path_name = path_from_string(s_name);
if (path_name.has_parent_path()) {
BOOST_LOG_TRIVIAL(warning) << "invalid name for config file (paths are not allowed): " << path_name.c_str();
return ret;
}
if (path_name.empty()) {
BOOST_LOG_TRIVIAL(warning) << "invalid: empty name for config file";
return ret;
}
// required file extension for any file to be recognized as a config file
path_name = path_from_string(s_name+".conf");
std::filesystem::path path;
path = valid_input_file(path_name, wxGetApp().GetConfigDir());
if (!path.empty()) {
ret = new std::ifstream(path);
if (ret->is_open()) {
return ret; // Found specified config file in user area of preferences
}
delete ret;
ret = nullptr;
}
path = valid_input_file(path_name, wxGetApp().GetResDir());
if (!path.empty()) {
ret = new std::ifstream(path);
if (ret->is_open()) {
return ret; // Found specified config file in built-in area of preferences
}
delete ret;
ret = nullptr;
}
return ret;
}
std::ifstream *Config::openFileExternal(const std::filesystem::path& path) {
std::ifstream *ret = nullptr;
const std::filesystem::path p = valid_input_file(path);
if (!p.empty()) {
ret = new std::ifstream(p);
if (ret->is_open()) {
return ret;
}
delete ret;
ret = nullptr;
}
return ret;
}
static const std::array rs_path_legacy{
"./etc/epple2/epple2.conf",
ETCDIR "/epple2/epple2.conf",
"/etc/epple2/epple2.conf",
"/etc/epple2.conf",
"./epple2.conf",
};
std::ifstream *Config::openFileLegacy() {
std::ifstream *ret = nullptr;
BOOST_LOG_TRIVIAL(warning) << "Searching for config file in legacy locations...";
for (const auto &s_path_legacy : rs_path_legacy) {
if ((ret = openFileExternal(std::filesystem::path{s_path_legacy})) != nullptr) {
return ret;
}
}
BOOST_LOG_TRIVIAL(warning) << "Could not file config file in any legacy locations.";
return ret;
}
/*
* Algorithm to locate and open the configuration file, as specified by
* the user, either on command line, or via preferences, allowing for
* backward compatibility with legacy file locations.
*/
std::ifstream *Config::openFile() {
std::ifstream *ret = nullptr;
if (this->file_path.empty()) {
BOOST_LOG_TRIVIAL(info) << "No config file specified on command line.";
wxString cname{};
const bool stored_prefs_found = wxConfigBase::Get()->Read("/ActivePreferences/name", &cname, DEFAULT_CONFIG_NAME);
if (stored_prefs_found) {
ret = openFilePref(cname);
if (ret != nullptr) {
return ret;
}
BOOST_LOG_TRIVIAL(warning) << "Could not find the previously chosen active config file: " << cname.wc_str();
if (!this->prefs_only) {
ret = openFileLegacy();
if (ret != nullptr) {
return ret;
}
}
} else {
if (!this->prefs_only) {
BOOST_LOG_TRIVIAL(warning) << "User has not specified any active config file.";
ret = openFileLegacy();
if (ret != nullptr) {
return ret;
}
}
ret = openFilePref(cname);
if (ret != nullptr) {
return ret;
}
}
} else {
BOOST_LOG_TRIVIAL(info) << "Config file specified on command line as: " << this->file_path.c_str();
if (!this->prefs_only) {
BOOST_LOG_TRIVIAL(info) << "Searching for external configuration file.";
ret = openFileExternal(this->file_path);
if (ret != nullptr) {
return ret;
}
}
ret = openFilePref(this->file_path.c_str());
if (ret != nullptr) {
return ret;
}
}
BOOST_LOG_TRIVIAL(warning) << "Could not find any config file.";
return ret;
}
void Config::parse(MemoryRandomAccess& ram, Memory& rom, Slots& slts, int& revision, ScreenImage& gui, CassetteIn& cassetteIn, CassetteOut& cassetteOut, Apple2* apple2) {
std::ifstream *p_ifstream_config = openFile();
if (p_ifstream_config == nullptr) {
std::cerr << "Cannot find config file. Running without any RAM, ROM, or cards." << std::endl;
return;
}
std::string line;
std::getline(*p_ifstream_config, line);
while (!p_ifstream_config->eof()) {
strip_comment(line);
trim(line);
if (!line.empty()) {
// TODO "parseLine" will become Command::execute, or similar
parseLine(line, ram, rom, slts, revision, gui, cassetteIn, cassetteOut, apple2);
}
std::getline(*p_ifstream_config, line);
}
delete p_ifstream_config;
// TODO: make sure there is no more than ONE stdin and/or ONE stdout card
}
void Config::parseLine(const std::string& line, MemoryRandomAccess& ram, Memory& rom, Slots& slts, int& revision, ScreenImage& gui, CassetteIn& cassetteIn, CassetteOut& cassetteOut, Apple2* apple2) {
try {
tryParseLine(line, ram, rom, slts, revision, gui, cassetteIn, cassetteOut, apple2);
} catch (const ConfigException& err) {
std::cerr << err.msg.c_str() << std::endl;
}
static void trim(std::string& s) {
wxString w{s};
s = w.Trim(false).Trim(true);
}
static std::string filter_row(const std::string &row) {
@ -331,7 +77,28 @@ static std::string filter_row(const std::string &row) {
return std::string(1, static_cast<char> (std::toupper(row[0])));
}
void Config::tryParseLine(const std::string& line, MemoryRandomAccess& ram, Memory& rom, Slots& slts, int& revision, ScreenImage& gui, CassetteIn& cassetteIn, CassetteOut& cassetteOut, Apple2* apple2) {
E2Command::E2Command() {
}
E2Command::~E2Command() {
}
void E2Command::parseLine(const std::string& line, MemoryRandomAccess& ram, Memory& rom, Slots& slts, int& revision, ScreenImage& gui, CassetteIn& cassetteIn, CassetteOut& cassetteOut, Apple2* apple2) {
try {
tryParseLine(line, ram, rom, slts, revision, gui, cassetteIn, cassetteOut, apple2);
} catch (const ConfigException& err) {
// TODO fix error handling
std::cerr << err.msg.c_str() << std::endl;
}
}
void E2Command::tryParseLine(const std::string& line, MemoryRandomAccess& ram, Memory& rom, Slots& slts, int& revision, ScreenImage& gui, CassetteIn& cassetteIn, CassetteOut& cassetteOut, Apple2* apple2) {
std::istringstream tok(line);
std::string cmd;
@ -341,7 +108,12 @@ void Config::tryParseLine(const std::string& line, MemoryRandomAccess& ram, Memo
std::string sCardType;
tok >> slot >> sCardType;
insertCard(sCardType, slot, slts, gui, tok);
double random_ones_rate(0.3); // WOZ spec v2.0: 30%
if (sCardType=="disk" || sCardType=="disk13") {
tok >> random_ones_rate;
}
insertCard(sCardType, slot, slts, gui, random_ones_rate);
} else if (cmd == "motherboard") {
std::string op;
tok >> op;
@ -505,10 +277,9 @@ void Config::tryParseLine(const std::string& line, MemoryRandomAccess& ram, Memo
trim(fn_optional);
if (fn_optional.length() == 0) {
gui.exitFullScreen();
char const *ft[1] = {"*.wav"};
char const *fn = tinyfd_openFileDialog("Load cassette audio", "", 1, ft, "WAVE cassette images", 0);
if (fn) {
fn_optional = std::string(fn);
wxFileDialog dlg{nullptr, "Load cassette (audio)", "", "", "WAVE cassette images (*.wav)|*.wav", wxFD_OPEN|wxFD_FILE_MUST_EXIST};
if (dlg.ShowModal() == wxID_OK) {
fn_optional = dlg.GetPath().c_str();
}
}
if (fn_optional.length() > 0) {
@ -552,73 +323,69 @@ void Config::tryParseLine(const std::string& line, MemoryRandomAccess& ram, Memo
unsigned char Config::disk_mask(0);
void Config::loadDisk(Slots& slts, int slot, int drive, const std::string& fnib) {
if (drive < 1 || 2 < drive) {
throw ConfigException("Invalid drive; must be 1 or 2");
}
// TODO if file doesn't exist, name still gets displayed, and there's no error message
void E2Command::loadDisk(Slots& slts, int slot, int drive, const std::filesystem::path &media) {
Card* card = slts.get(slot);
if (!(disk_mask & (1 << slot))) {
std::cerr << "Slot " << slot << " doesn't have a disk controller card" << std::endl;
if (!card->hasMedia()) {
// TODO if file doesn't exist, name still gets displayed, and there's no error message
// TODO error message
// std::cerr << "Slot " << slot << " doesn't have a disk controller card" << std::endl;
return;
}
DiskController* controller = (DiskController*) card;
controller->loadDisk(drive - 1, fnib);
}
void Config::unloadDisk(Slots& slts, int slot, int drive) {
if (drive < 1 || 2 < drive) {
throw ConfigException("Invalid drive; must be 1 or 2");
}
card->loadMedia(drive - 1, media);
}
void E2Command::unloadDisk(Slots& slts, int slot, int drive) {
Card* card = slts.get(slot);
if (!(disk_mask & (1 << slot))) {
std::cerr << "Slot " << slot << " doesn't have a disk controller card" << std::endl;
if (!card->hasMedia()) {
// TODO do we even need an error here?
// std::cerr << "Slot " << slot << " doesn't have a disk controller card" << std::endl;
return;
}
DiskController* controller = (DiskController*) card;
controller->unloadDisk(drive - 1);
}
void Config::saveDisk(Slots& slts, int slot, int drive) {
if (drive < 1 || 2 < drive) {
throw ConfigException("Invalid drive; must be 1 or 2");
}
slts.get(slot)->save(drive - 1);
card->unloadMedia(drive - 1);
}
void Config::insertCard(const std::string& cardType, int slot, Slots& slts, ScreenImage& gui, std::istringstream& tok) {
void E2Command::saveDisk(Slots& slts, int slot, int drive) {
Card* card = slts.get(slot);
if (!card->hasMedia()) {
// TODO do we even need an error here?
// std::cerr << "Slot " << slot << " doesn't have a disk controller card" << std::endl;
return;
}
if (drive < 1 || 2 < drive) {
throw ConfigException("Invalid drive; must be 1 or 2");
}
card->saveMedia(drive - 1);
}
void E2Command::insertCard(const std::string& cardType, const int slot, Slots& slts, ScreenImage& gui, const double random_ones_rate) {
if (slot < 0 || 8 <= slot) {
throw ConfigException("Invalid slot number");
}
Card* card;
disk_mask &= ~(1 << slot);
Card* card = nullptr;
if (cardType == "language") {
card = new LanguageCard(gui, slot);
} else if (cardType == "firmware") {
card = new FirmwareCard(gui, slot);
} else if (cardType == "disk") {
// 16-sector LSS ROM
double random_ones_rate(0.3); // WOZ spec v2.0: 30%
tok >> random_ones_rate;
std::cerr << "MC3470: rate of 1 bits during random bit generation: " << random_ones_rate << std::endl;
card = new DiskController(gui, slot, false, random_ones_rate);
disk_mask |= (1 << slot);
} else if (cardType == "disk13") {
// 13-sector LSS ROM
double random_ones_rate(0.3); // WOZ spec v2.0: 30%
tok >> random_ones_rate;
std::cerr << "MC3470: rate of 1 bits during random bit generation: " << random_ones_rate << std::endl;
card = new DiskController(gui, slot, true, random_ones_rate);
disk_mask |= (1 << slot);
} else if (cardType == "clock") {
card = new ClockCard();
} else if (cardType == "stdout") {

71
src/e2command.h Normal file
View File

@ -0,0 +1,71 @@
/*
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/>.
*/
/*
* File: e2command.h
* Author: user
*
* Created on December 11, 2022, 8:21 PM
*/
#ifndef E2COMMAND_H
#define E2COMMAND_H
#include <filesystem>
#include <sstream>
#include <string>
class MemoryRandomAccess;
class Memory;
class Slots;
class ScreenImage;
class CassetteIn;
class CassetteOut;
class Apple2;
class ConfigException {
public:
const std::string msg;
ConfigException(const std::string& msg) : msg(msg) {}
};
class E2Command {
void tryParseLine(const std::string& line, MemoryRandomAccess& ram, Memory& rom, Slots& slts, int& revision, ScreenImage& gui, CassetteIn& cassetteIn, CassetteOut& cassetteOut, Apple2* apple2);
void insertCard(const std::string& cardType, const int slot, Slots& slts, ScreenImage& gui, const double random_ones_rate);
void loadDisk(Slots& slts, int slot, int drive, const std::filesystem::path &media);
void unloadDisk(Slots& slts, int slot, int drive);
void saveDisk(Slots& slts, int slot, int drive);
public:
E2Command();
virtual ~E2Command();
void parseLine(const std::string& line, MemoryRandomAccess& ram, Memory& rom, Slots& slts, int& revision, ScreenImage& gui, CassetteIn& cassetteIn, CassetteOut& cassetteOut, Apple2* apple2);
};
#endif /* E2COMMAND_H */

266
src/e2config.cpp Normal file
View File

@ -0,0 +1,266 @@
/*
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 "e2config.h"
#include "e2command.h"
#include "E2wxApp.h"
#include "e2filesystem.h"
#include "apple2.h"
#include "memory.h"
#include "memoryrandomaccess.h"
#include "slots.h"
#include "diskcontroller.h"
#include "languagecard.h"
#include "firmwarecard.h"
#include "standardout.h"
#include "standardin.h"
#include "clockcard.h"
#include "cassettein.h"
#include "cassetteout.h"
#include <wx/filedlg.h>
#include <wx/config.h>
#include <wx/string.h>
#include <boost/log/trivial.hpp>
#include <filesystem>
#include <iostream>
#include <istream>
#include <fstream>
#include <sstream>
#include <string>
#include <stdexcept>
#include <cctype>
static const wxString DEFAULT_CONFIG_NAME{"epple2"};
E2Config::E2Config(const std::filesystem::path& f, bool p): file_path {f}, prefs_only {p} {
}
E2Config::~E2Config() {
}
static void strip_comment(std::string& str) {
const size_t comment = str.find('#');
if (comment < std::string::npos) {
str.erase(comment);
}
}
static void trim(std::string& s) {
wxString w{s};
s = w.Trim(false).Trim(true);
}
/*
* Searches for config file with the given name in the preferences areas.
* The name must not be a filesystem path.
* The actual config files themselves have ".conf" extension.
* The preferences areas are searched in this order:
* user area
* built-in area
*
* If successful, returns an open stream, caller is responsible for deleting it
* Otherwise, returns null
*/
std::ifstream *E2Config::openFilePref(const wxString& s_name) {
std::ifstream *ret = nullptr;
BOOST_LOG_TRIVIAL(info) << "config file name was specified as: " << s_name;
std::filesystem::path path_name = path_from_string(s_name);
if (path_name.has_parent_path()) {
BOOST_LOG_TRIVIAL(warning) << "invalid name for config file (paths are not allowed): " << path_name.c_str();
return ret;
}
if (path_name.empty()) {
BOOST_LOG_TRIVIAL(warning) << "invalid: empty name for config file";
return ret;
}
// required file extension for any file to be recognized as a config file
path_name = path_from_string(s_name+".conf");
std::filesystem::path path;
path = valid_input_file(path_name, wxGetApp().GetConfigDir());
if (!path.empty()) {
ret = new std::ifstream(path);
if (ret->is_open()) {
return ret; // Found specified config file in user area of preferences
}
delete ret;
ret = nullptr;
}
path = valid_input_file(path_name, wxGetApp().GetResDir());
if (!path.empty()) {
ret = new std::ifstream(path);
if (ret->is_open()) {
return ret; // Found specified config file in built-in area of preferences
}
delete ret;
ret = nullptr;
}
return ret;
}
std::ifstream *E2Config::openFileExternal(const std::filesystem::path& path) {
std::ifstream *ret = nullptr;
const std::filesystem::path p = valid_input_file(path);
if (!p.empty()) {
ret = new std::ifstream(p);
if (ret->is_open()) {
return ret;
}
delete ret;
ret = nullptr;
}
return ret;
}
static const std::array rs_path_legacy{
"./etc/epple2/epple2.conf",
ETCDIR "/epple2/epple2.conf",
"/etc/epple2/epple2.conf",
"/etc/epple2.conf",
"./epple2.conf",
};
std::ifstream *E2Config::openFileLegacy() {
std::ifstream *ret = nullptr;
BOOST_LOG_TRIVIAL(warning) << "Searching for config file in legacy locations...";
for (const auto &s_path_legacy : rs_path_legacy) {
if ((ret = openFileExternal(std::filesystem::path{s_path_legacy})) != nullptr) {
return ret;
}
}
BOOST_LOG_TRIVIAL(warning) << "Could not file config file in any legacy locations.";
return ret;
}
/*
* Algorithm to locate and open the configuration file, as specified by
* the user, either on command line, or via preferences, allowing for
* backward compatibility with legacy file locations.
*/
std::ifstream *E2Config::openFile() {
std::ifstream *ret = nullptr;
if (this->file_path.empty()) {
BOOST_LOG_TRIVIAL(info) << "No config file specified on command line.";
wxString cname{};
const bool stored_prefs_found = wxConfigBase::Get()->Read("/ActivePreferences/name", &cname, DEFAULT_CONFIG_NAME);
if (stored_prefs_found) {
BOOST_LOG_TRIVIAL(info) << "Will use config file name currently marked as active by the user: " << cname.wc_str();
ret = openFilePref(cname);
if (ret != nullptr) {
return ret;
}
BOOST_LOG_TRIVIAL(warning) << "Could not find the config file currently marked as active by the user: " << cname.wc_str();
if (!this->prefs_only) {
ret = openFileLegacy();
if (ret != nullptr) {
return ret;
}
}
} else {
if (!this->prefs_only) {
BOOST_LOG_TRIVIAL(warning) << "User has not specified any active config file.";
ret = openFileLegacy();
if (ret != nullptr) {
return ret;
}
}
ret = openFilePref(cname);
if (ret != nullptr) {
return ret;
}
}
} else {
BOOST_LOG_TRIVIAL(info) << "Config file specified on command line as: " << this->file_path.c_str();
if (!this->prefs_only) {
BOOST_LOG_TRIVIAL(info) << "Searching for external configuration file.";
ret = openFileExternal(this->file_path);
if (ret != nullptr) {
return ret;
}
}
ret = openFilePref(this->file_path.c_str());
if (ret != nullptr) {
return ret;
}
}
BOOST_LOG_TRIVIAL(warning) << "Could not find any config file.";
return ret;
}
void E2Config::parse(MemoryRandomAccess& ram, Memory& rom, Slots& slts, int& revision, ScreenImage& gui, CassetteIn& cassetteIn, CassetteOut& cassetteOut, Apple2* apple2) {
std::ifstream *p_ifstream_config = openFile();
if (p_ifstream_config == nullptr) {
std::cerr << "Cannot find config file. Running without any RAM, ROM, or cards." << std::endl;
return;
}
std::string line;
std::getline(*p_ifstream_config, line);
while (!p_ifstream_config->eof()) {
strip_comment(line);
trim(line);
if (!line.empty()) {
E2Command{}.parseLine(line, ram, rom, slts, revision, gui, cassetteIn, cassetteOut, apple2);
}
std::getline(*p_ifstream_config, line);
}
delete p_ifstream_config;
// TODO: make sure there is no more than ONE stdin and/or ONE stdout card
}

View File

@ -29,37 +29,25 @@ class CassetteIn;
class CassetteOut;
class Apple2;
class ConfigException {
public:
const std::string msg;
ConfigException(const std::string& msg) : msg(msg) {}
};
// TODO split out all static things into their own class (and don't make them static)
// Remember that, besides config, also command line entry calls parseLine
// This will also help with adding menu items in place of commands
class Config {
class E2Config {
private:
const std::filesystem::path file_path;
const bool prefs_only;
static unsigned char disk_mask;
std::ifstream *openFile();
std::ifstream *openFilePref(const wxString& s_name);
std::ifstream *openFileExternal(const std::filesystem::path& path);
std::ifstream *openFileLegacy();
static void loadDisk(Slots& slts, int slot, int drive, const std::string& fnib);
static void unloadDisk(Slots& slts, int slot, int drive);
static void saveDisk(Slots& slts, int slot, int drive);
static void insertCard(const std::string& cardType, int slot, Slots& slts, ScreenImage& gui, std::istringstream& tok);
static void tryParseLine(const std::string& line, MemoryRandomAccess& ram, Memory& rom, Slots& slts, int& revision, ScreenImage& gui, CassetteIn& cassetteIn, CassetteOut& cassetteOut, Apple2* apple2);
public:
Config(const std::filesystem::path& f, bool p);
~Config();
E2Config(const std::filesystem::path& f, bool p);
~E2Config();
void parse(MemoryRandomAccess& ram, Memory& rom, Slots& slts, int& revision, ScreenImage& gui, CassetteIn& cassetteIn, CassetteOut& cassetteOut, Apple2* apple2);
static void parseLine(const std::string& line, MemoryRandomAccess& ram, Memory& rom, Slots& slts, int& revision, ScreenImage& gui, CassetteIn& cassetteIn, CassetteOut& cassetteOut, Apple2* apple2);
};
#endif

View File

@ -30,7 +30,7 @@
#include <system_error>
static void log_ec(const std::error_code& ec) {
BOOST_LOG_TRIVIAL(error) << "error: " << ec.value() << " " << ec.message();
BOOST_LOG_TRIVIAL(warning) << "error from native file-system: " << ec.value() << " " << ec.message();
}
std::filesystem::path valid_input_file(const std::filesystem::path path, const std::filesystem::path base) {
@ -69,11 +69,11 @@ std::filesystem::path valid_input_file(const std::filesystem::path path, const s
}
if (!exists) {
BOOST_LOG_TRIVIAL(error) << "can't find file: " << p.c_str();
BOOST_LOG_TRIVIAL(warning) << "can't find file: " << p.c_str();
p = std::filesystem::weakly_canonical(p, ec);
if (ec) {
log_ec(ec);
BOOST_LOG_TRIVIAL(error) << "can't determine canonical path.";
BOOST_LOG_TRIVIAL(warning) << "can't determine canonical path.";
} else {
BOOST_LOG_TRIVIAL(info) << "...canonical file path was: " << p.c_str();
}
@ -88,7 +88,7 @@ std::filesystem::path valid_input_file(const std::filesystem::path path, const s
std::filesystem::path cp = std::filesystem::canonical(p, ec);
if (ec) {
log_ec(ec);
BOOST_LOG_TRIVIAL(error) << "can't determine canonical path, continuing anyway...";
BOOST_LOG_TRIVIAL(warning) << "can't determine canonical path, continuing anyway...";
} else {
BOOST_LOG_TRIVIAL(info) << "...canonical file path was: " << cp;
}
@ -102,7 +102,7 @@ std::filesystem::path valid_input_file(const std::filesystem::path path, const s
}
if (!regular) {
BOOST_LOG_TRIVIAL(error) << "not a regular file";
BOOST_LOG_TRIVIAL(warning) << "not a regular file";
p.clear();
return p;
}

View File

@ -16,9 +16,12 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "emulator.h"
#include "configep2.h"
#include "E2wxApp.h"
#include "e2config.h"
#include "e2command.h"
#include "e2const.h"
#include "tinyfiledialogs.h"
#include <wx/msgdlg.h>
#include <SDL.h>
@ -63,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);
@ -73,7 +75,7 @@ void Emulator::powerOffComputer() {
this->timable = &this->videoStatic;
}
void Emulator::config(Config& cfg) {
void Emulator::config(E2Config& cfg) {
cfg.parse(this->apple2.ram, this->apple2.rom, this->apple2.slts, this->apple2.revision, this->screenImage, this->apple2.cassetteIn, this->apple2.cassetteOut, &this->apple2);
this->apple2.ram.dump_config();
}
@ -83,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() {
@ -132,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
@ -143,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
@ -164,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) {
@ -220,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 ?
}
@ -411,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) {
@ -478,12 +428,25 @@ void Emulator::processCommand() {
return;
}
Config::parseLine(cmdline, this->apple2.ram, this->apple2.rom, this->apple2.slts, this->apple2.revision, this->screenImage, this->apple2.cassetteIn, this->apple2.cassetteOut, NULL);
E2Command{}.parseLine(cmdline, this->apple2.ram, this->apple2.rom, this->apple2.slts, this->apple2.revision, this->screenImage, this->apple2.cassetteIn, this->apple2.cassetteOut, NULL);
cmdline.erase(cmdline.begin(), cmdline.end());
}
static int askSave() {
wxMessageDialog *dlg = new wxMessageDialog{
nullptr,
"You have unsaved changes to your floppy disk images.\nDo you want to SAVE them?",
"Save changes",
(long)(wxYES_NO|wxCANCEL|wxCANCEL_DEFAULT)};
return dlg->ShowModal();
}
bool Emulator::isSafeToQuit() {
this->screenImage.exitFullScreen();
if (!this->apple2.cassetteOut.eject()) {
// TODO does this handle the case where we fail to save?
return false;
}
@ -491,28 +454,23 @@ bool Emulator::isSafeToQuit() {
return true;
}
const int resp = tinyfd_messageBox(
"Save changes",
"You have unsaved changes to your floppy disk images.\nDo you want to SAVE them?",
"yesnocancel",
"warning",
0);
const int resp = askSave();
if (resp == 0) { // cancel
if (resp == wxID_CANCEL) {
return false;
}
if (resp == 1) { // yes (save)
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;
}
void Emulator::handleUserQuitRequest() {
wxGetApp().CloseMainFrame();
}

View File

@ -30,10 +30,9 @@
#include <SDL.h>
class Timable;
class Config;
class E2Config;
class Emulator
{
class Emulator {
PaddleButtonStates paddleButtonStates;
KeypressQueue keypresses;
@ -58,32 +57,29 @@ 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();
virtual ~Emulator();
void config(Config& cfg);
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();
};

View File

@ -18,16 +18,8 @@
*/
#include "gui.h"
#include "e2const.h"
#include <SDL.h>
#include <wx/app.h>
#include <iostream>
#include <ostream>
#include <stdexcept>
#include <cstdio>
@ -38,12 +30,6 @@ int main(int argc, char *argv[]) {
::setbuf(stdout, nullptr);
::setbuf(stderr, nullptr);
const int x = E2Const::test();
if (x != -1) {
std::cerr << x << std::endl;
throw std::runtime_error("bad constant in e2const.h" );
}
GUI gui;
return wxEntry(argc, argv);

View File

@ -21,6 +21,7 @@
#include "card.h"
#include "util.h"
#include <SDL.h>
#include <filesystem>
#include <iostream>
#include <ctime>
#include <sstream>
@ -366,7 +367,7 @@ void ScreenImage::removeCard(const int slot, Card* card /* empty */) {
789012345678901234567890123456789012345678901234567890123456789012345
6: disk][ drive 1M*filename.nib T$FF drive 2M*filename.nib T$FF
*/
void ScreenImage::setDiskFile(int slot, int drive, const std::string& filepath) {
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);
@ -382,16 +383,8 @@ void ScreenImage::setDiskFile(int slot, int drive, const std::string& filepath)
this->slotnames[slot].replace(c - 20, f.length(), f);
}
std::string ScreenImage::truncateFilePath(const std::string& filepath) {
std::string f(filepath);
size_t slash = f.find_last_of("/\\");
if (slash != std::string::npos) {
f = f.substr(slash + 1);
}
if (f.length() > 12) {
f = f.substr(0, 12);
}
return f;
std::string ScreenImage::truncateFilePath(const std::filesystem::path& filepath) {
return filepath.stem().string().substr(0, 12);
}
void ScreenImage::clearCurrentDrive(int slot, int drive) {
@ -453,7 +446,7 @@ void ScreenImage::setDirty(int slot, int drive, bool dirty) {
this->slotnames[slot][c - 20] = dirty ? '*' : ' ';
}
void ScreenImage::setCassetteInFile(const std::string& filepath) {
void ScreenImage::setCassetteInFile(const std::filesystem::path& filepath) {
std::string f = truncateFilePath(filepath);
int r(65);
int c(85 + 11);
@ -469,7 +462,7 @@ void ScreenImage::setCassetteInFile(const std::string& filepath) {
this->cassInName.replace(c - 94, f.length(), f);
}
void ScreenImage::setCassetteOutFile(const std::string& filepath) {
void ScreenImage::setCassetteOutFile(const std::filesystem::path& filepath) {
std::string f = truncateFilePath(filepath);
int r(66);
int c(85 + 11);

View File

@ -19,6 +19,7 @@
#define SCREENIMAGE_H
#include "analogtv.h"
#include <filesystem>
#include <vector>
#include <string>
@ -45,7 +46,7 @@ private:
std::string cassInName;
std::string cassOutName;
static std::string truncateFilePath(const std::string& filepath);
static std::string truncateFilePath(const std::filesystem::path& filepath);
// TODO some of these methods should be private
public:
@ -80,7 +81,7 @@ public:
void addkeyCommand(unsigned char key);
void backspaceCommand();
void setDiskFile(int slot, int drive, const std::string& filename);
void setDiskFile(int slot, int drive, const std::filesystem::path& filename);
void setAnnunciator(int ann, bool on);
@ -90,8 +91,8 @@ public:
void setIO(int slot, int drive, bool on);
void setDirty(int slot, int drive, bool dirty);
void setCassetteInFile(const std::string& filepath);
void setCassetteOutFile(const std::string& filepath);
void setCassetteInFile(const std::filesystem::path& filepath);
void setCassetteOutFile(const std::filesystem::path& filepath);
void setCassetteDirty(bool dirty); // cassette out only
void setCassettePos(unsigned int pos, unsigned int siz); // cassette in only

View File

@ -134,8 +134,8 @@ void Slots::forceGuiUpdate()
}
void Slots::save(int unit) {
for (std::vector<Card*>::iterator i = this->cards.begin(); i != this->cards.end(); ++i) {
(*i)->save(unit);
for (auto &i : this->cards) {
i->saveMedia(unit);
}
}
@ -154,14 +154,12 @@ bool isAnyDiskDriveMotorOn()
return on.inhibit;
}
*/
struct Slots_Card_isDirty
{
bool dirty;
Slots_Card_isDirty():dirty(false) {}
void operator() (Card* p) { if (p->isDirty()) dirty = true; }
};
bool Slots::isDirty()
{
return std::for_each(this->cards.begin(),this->cards.end(),Slots_Card_isDirty()).dirty;
bool Slots::isDirty() {
for (auto &i : this->cards) {
if (i->isMediaDirty()) {
return true;
}
}
return false;
}

View File

@ -24,9 +24,7 @@
class ScreenImage;
class Slots
{
private:
class Slots {
ScreenImage& gui;
EmptySlot empty;
std::vector<Card*> cards;
@ -35,7 +33,7 @@ public:
Slots(ScreenImage& gui);
~Slots();
void tick();
void tick();
unsigned char io(const int islot, const int iswch, const unsigned char b, const bool writing);
void reset();
unsigned char readRom(const int islot, const unsigned short addr, const unsigned char data);
@ -46,8 +44,8 @@ public:
Card* get(const int slot);
void remove(const int slot);
bool isDirty();
void save(int unit);
void forceGuiUpdate();
void save(int unit);
void forceGuiUpdate();
};
#endif

File diff suppressed because it is too large Load Diff

View File

@ -1,303 +0,0 @@
/* If you are using a C++ compiler to compile tinyfiledialogs.c (maybe renamed with an extension ".cpp")
then comment out << extern "C" >> bellow in this header file) */
/*_________
/ \ tinyfiledialogs.h v3.8.9 [Oct 27, 2022] zlib licence
|tiny file| Unique header file created [November 9, 2014]
| dialogs | Copyright (c) 2014 - 2021 Guillaume Vareille http://ysengrin.com
\____ ___/ http://tinyfiledialogs.sourceforge.net
\| git clone http://git.code.sf.net/p/tinyfiledialogs/code tinyfd
____________________________________________
| |
| email: tinyfiledialogs at ysengrin.com |
|____________________________________________|
________________________________________________________________________________
| ____________________________________________________________________________ |
| | | |
| | on windows: | |
| | - for UTF-16, use the wchar_t functions at the bottom of the header file | |
| | - _wfopen() requires wchar_t | |
| | | |
| | - in tinyfiledialogs, char is UTF-8 by default (since v3.6) | |
| | - but fopen() expects MBCS (not UTF-8) | |
| | - if you want char to be MBCS: set tinyfd_winUtf8 to 0 | |
| | | |
| | - alternatively, tinyfiledialogs provides | |
| | functions to convert between UTF-8, UTF-16 and MBCS | |
| |____________________________________________________________________________| |
|________________________________________________________________________________|
If you like tinyfiledialogs, please upvote my stackoverflow answer
https://stackoverflow.com/a/47651444
- License -
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#ifndef TINYFILEDIALOGS_H
#define TINYFILEDIALOGS_H
#ifdef __cplusplus
/* if tinydialogs.c is compiled as C++ code rather than C code, you may need to comment this out
and the corresponding closing bracket near the end of this file. */
extern "C" {
#endif
/******************************************************************************************************/
/**************************************** UTF-8 on Windows ********************************************/
/******************************************************************************************************/
#ifdef _WIN32
/* On windows, if you want to use UTF-8 ( instead of the UTF-16/wchar_t functions at the end of this file )
Make sure your code is really prepared for UTF-8 (on windows, functions like fopen() expect MBCS and not UTF-8) */
extern int tinyfd_winUtf8; /* on windows char strings can be 1:UTF-8(default) or 0:MBCS */
/* for MBCS change this to 0, in tinyfiledialogs.c or in your code */
/* Here are some functions to help you convert between UTF-16 UTF-8 MBSC */
char * tinyfd_utf8toMbcs(char const * aUtf8string);
char * tinyfd_utf16toMbcs(wchar_t const * aUtf16string);
wchar_t * tinyfd_mbcsTo16(char const * aMbcsString);
char * tinyfd_mbcsTo8(char const * aMbcsString);
wchar_t * tinyfd_utf8to16(char const * aUtf8string);
char * tinyfd_utf16to8(wchar_t const * aUtf16string);
#endif
/******************************************************************************************************/
/******************************************************************************************************/
/******************************************************************************************************/
/************* 3 funtions for C# (you don't need this in C or C++) : */
char const * tinyfd_getGlobalChar(char const * aCharVariableName); /* returns NULL on error */
int tinyfd_getGlobalInt(char const * aIntVariableName); /* returns -1 on error */
int tinyfd_setGlobalInt(char const * aIntVariableName, int aValue); /* returns -1 on error */
/* aCharVariableName: "tinyfd_version" "tinyfd_needs" "tinyfd_response"
aIntVariableName : "tinyfd_verbose" "tinyfd_silent" "tinyfd_allowCursesDialogs"
"tinyfd_forceConsole" "tinyfd_assumeGraphicDisplay" "tinyfd_winUtf8"
**************/
extern char tinyfd_version[8]; /* contains tinyfd current version number */
extern char tinyfd_needs[]; /* info about requirements */
extern int tinyfd_verbose; /* 0 (default) or 1 : on unix, prints the command line calls */
extern int tinyfd_silent; /* 1 (default) or 0 : on unix, hide errors and warnings from called dialogs */
/* Curses dialogs are difficult to use, on windows they are only ascii and uses the unix backslah */
extern int tinyfd_allowCursesDialogs; /* 0 (default) or 1 */
extern int tinyfd_forceConsole; /* 0 (default) or 1 */
/* for unix & windows: 0 (graphic mode) or 1 (console mode).
0: try to use a graphic solution, if it fails then it uses console mode.
1: forces all dialogs into console mode even when an X server is present,
it can use the package dialog or dialog.exe.
on windows it only make sense for console applications */
extern int tinyfd_assumeGraphicDisplay; /* 0 (default) or 1 */
/* some systems don't set the environment variable DISPLAY even when a graphic display is present.
set this to 1 to tell tinyfiledialogs to assume the existence of a graphic display */
extern char tinyfd_response[1024];
/* if you pass "tinyfd_query" as aTitle,
the functions will not display the dialogs
but will return 0 for console mode, 1 for graphic mode.
tinyfd_response is then filled with the retain solution.
possible values for tinyfd_response are (all lowercase)
for graphic mode:
windows_wchar windows applescript kdialog zenity zenity3 matedialog
shellementary qarma yad python2-tkinter python3-tkinter python-dbus
perl-dbus gxmessage gmessage xmessage xdialog gdialog
for console mode:
dialog whiptail basicinput no_solution */
void tinyfd_beep(void);
int tinyfd_notifyPopup(
char const * aTitle, /* NULL or "" */
char const * aMessage, /* NULL or "" may contain \n \t */
char const * aIconType); /* "info" "warning" "error" */
/* return has only meaning for tinyfd_query */
int tinyfd_messageBox(
char const * aTitle , /* NULL or "" */
char const * aMessage , /* NULL or "" may contain \n \t */
char const * aDialogType , /* "ok" "okcancel" "yesno" "yesnocancel" */
char const * aIconType , /* "info" "warning" "error" "question" */
int aDefaultButton ) ;
/* 0 for cancel/no , 1 for ok/yes , 2 for no in yesnocancel */
char * tinyfd_inputBox(
char const * aTitle , /* NULL or "" */
char const * aMessage , /* NULL or "" (\n and \t have no effect) */
char const * aDefaultInput ) ; /* NULL passwordBox, "" inputbox */
/* returns NULL on cancel */
char * tinyfd_saveFileDialog(
char const * aTitle , /* NULL or "" */
char const * aDefaultPathAndFile , /* NULL or "" */
int aNumOfFilterPatterns , /* 0 (1 in the following example) */
char const * const * aFilterPatterns , /* NULL or char const * lFilterPatterns[1]={"*.txt"} */
char const * aSingleFilterDescription ) ; /* NULL or "text files" */
/* returns NULL on cancel */
char * tinyfd_openFileDialog(
char const * aTitle, /* NULL or "" */
char const * aDefaultPathAndFile, /* NULL or "" */
int aNumOfFilterPatterns , /* 0 (2 in the following example) */
char const * const * aFilterPatterns, /* NULL or char const * lFilterPatterns[2]={"*.png","*.jpg"}; */
char const * aSingleFilterDescription, /* NULL or "image files" */
int aAllowMultipleSelects ) ; /* 0 or 1 */
/* in case of multiple files, the separator is | */
/* returns NULL on cancel */
char * tinyfd_selectFolderDialog(
char const * aTitle, /* NULL or "" */
char const * aDefaultPath); /* NULL or "" */
/* returns NULL on cancel */
char * tinyfd_colorChooser(
char const * aTitle, /* NULL or "" */
char const * aDefaultHexRGB, /* NULL or "#FF0000" */
unsigned char const aDefaultRGB[3] , /* unsigned char lDefaultRGB[3] = { 0 , 128 , 255 }; */
unsigned char aoResultRGB[3] ) ; /* unsigned char lResultRGB[3]; */
/* returns the hexcolor as a string "#FF0000" */
/* aoResultRGB also contains the result */
/* aDefaultRGB is used only if aDefaultHexRGB is NULL */
/* aDefaultRGB and aoResultRGB can be the same array */
/* returns NULL on cancel */
/************ WINDOWS ONLY SECTION ************************/
#ifdef _WIN32
/* windows only - utf-16 version */
int tinyfd_notifyPopupW(
wchar_t const * aTitle, /* NULL or L"" */
wchar_t const * aMessage, /* NULL or L"" may contain \n \t */
wchar_t const * aIconType); /* L"info" L"warning" L"error" */
/* windows only - utf-16 version */
int tinyfd_messageBoxW(
wchar_t const * aTitle, /* NULL or L"" */
wchar_t const * aMessage, /* NULL or L"" may contain \n \t */
wchar_t const * aDialogType, /* L"ok" L"okcancel" L"yesno" */
wchar_t const * aIconType, /* L"info" L"warning" L"error" L"question" */
int aDefaultButton ); /* 0 for cancel/no , 1 for ok/yes */
/* returns 0 for cancel/no , 1 for ok/yes */
/* windows only - utf-16 version */
wchar_t * tinyfd_inputBoxW(
wchar_t const * aTitle, /* NULL or L"" */
wchar_t const * aMessage, /* NULL or L"" (\n nor \t not respected) */
wchar_t const * aDefaultInput); /* NULL passwordBox, L"" inputbox */
/* windows only - utf-16 version */
wchar_t * tinyfd_saveFileDialogW(
wchar_t const * aTitle, /* NULL or L"" */
wchar_t const * aDefaultPathAndFile, /* NULL or L"" */
int aNumOfFilterPatterns, /* 0 (1 in the following example) */
wchar_t const * const * aFilterPatterns, /* NULL or wchar_t const * lFilterPatterns[1]={L"*.txt"} */
wchar_t const * aSingleFilterDescription); /* NULL or L"text files" */
/* returns NULL on cancel */
/* windows only - utf-16 version */
wchar_t * tinyfd_openFileDialogW(
wchar_t const * aTitle, /* NULL or L"" */
wchar_t const * aDefaultPathAndFile, /* NULL or L"" */
int aNumOfFilterPatterns , /* 0 (2 in the following example) */
wchar_t const * const * aFilterPatterns, /* NULL or wchar_t const * lFilterPatterns[2]={L"*.png","*.jpg"} */
wchar_t const * aSingleFilterDescription, /* NULL or L"image files" */
int aAllowMultipleSelects ) ; /* 0 or 1 */
/* in case of multiple files, the separator is | */
/* returns NULL on cancel */
/* windows only - utf-16 version */
wchar_t * tinyfd_selectFolderDialogW(
wchar_t const * aTitle, /* NULL or L"" */
wchar_t const * aDefaultPath); /* NULL or L"" */
/* returns NULL on cancel */
/* windows only - utf-16 version */
wchar_t * tinyfd_colorChooserW(
wchar_t const * aTitle, /* NULL or L"" */
wchar_t const * aDefaultHexRGB, /* NULL or L"#FF0000" */
unsigned char const aDefaultRGB[3], /* unsigned char lDefaultRGB[3] = { 0 , 128 , 255 }; */
unsigned char aoResultRGB[3]); /* unsigned char lResultRGB[3]; */
/* returns the hexcolor as a string L"#FF0000" */
/* aoResultRGB also contains the result */
/* aDefaultRGB is used only if aDefaultHexRGB is NULL */
/* aDefaultRGB and aoResultRGB can be the same array */
/* returns NULL on cancel */
#endif /*_WIN32 */
#ifdef __cplusplus
} /*extern "C"*/
#endif
#endif /* TINYFILEDIALOGS_H */
/*
________________________________________________________________________________
| ____________________________________________________________________________ |
| | | |
| | on windows: | |
| | - for UTF-16, use the wchar_t functions at the bottom of the header file | |
| | - _wfopen() requires wchar_t | |
| | | |
| | - in tinyfiledialogs, char is UTF-8 by default (since v3.6) | |
| | - but fopen() expects MBCS (not UTF-8) | |
| | - if you want char to be MBCS: set tinyfd_winUtf8 to 0 | |
| | | |
| | - alternatively, tinyfiledialogs provides | |
| | functions to convert between UTF-8, UTF-16 and MBCS | |
| |____________________________________________________________________________| |
|________________________________________________________________________________|
- This is not for ios nor android (it works in termux though).
- The files can be renamed with extension ".cpp" as the code is 100% compatible C C++
(just comment out << extern "C" >> in the header file)
- Windows is fully supported from XP to 10 (maybe even older versions)
- C# & LUA via dll, see files in the folder EXTRAS
- OSX supported from 10.4 to latest (maybe even older versions)
- Do not use " and ' as the dialogs will be displayed with a warning
instead of the title, message, etc...
- There's one file filter only, it may contain several patterns.
- If no filter description is provided,
the list of patterns will become the description.
- On windows link against Comdlg32.lib and Ole32.lib
(on windows the no linking claim is a lie)
- On unix: it tries command line calls, so no such need (NO LINKING).
- On unix you need one of the following:
applescript, kdialog, zenity, matedialog, shellementary, qarma, yad,
python (2 or 3)/tkinter/python-dbus (optional), Xdialog
or curses dialogs (opens terminal if running without console).
- One of those is already included on most (if not all) desktops.
- In the absence of those it will use gdialog, gxmessage or whiptail
with a textinputbox. If nothing is found, it switches to basic console input,
it opens a console if needed (requires xterm + bash).
- for curses dialogs you must set tinyfd_allowCursesDialogs=1
- You can query the type of dialog that will be used (pass "tinyfd_query" as aTitle)
- String memory is preallocated statically for all the returned values.
- File and path names are tested before return, they should be valid.
- tinyfd_forceConsole=1; at run time, forces dialogs into console mode.
- On windows, console mode only make sense for console applications.
- On windows, console mode is not implemented for wchar_T UTF-16.
- Mutiple selects are not possible in console mode.
- The package dialog must be installed to run in curses dialogs in console mode.
It is already installed on most unix systems.
- On osx, the package dialog can be installed via
http://macappstore.org/dialog or http://macports.org
- On windows, for curses dialogs console mode,
dialog.exe should be copied somewhere on your executable path.
It can be found at the bottom of the following page:
http://andrear.altervista.org/home/cdialog.php
*/