visualboyadvance-m/src/wx/wxvbam.cpp

1157 lines
36 KiB
C++

// mainline:
// parse cmd line
// load xrc file (guiinit.cpp does most of instantiation)
// create & display main frame
#include "wxvbam.h"
#include <stdio.h>
#include <wx/cmdline.h>
#include <wx/file.h>
#include <wx/filesys.h>
#include <wx/fs_arc.h>
#include <wx/fs_mem.h>
#include <wx/mstream.h>
#include <wx/progdlg.h>
#include <wx/protocol/http.h>
#include <wx/regex.h>
#include <wx/sstream.h>
#include <wx/txtstrm.h>
#include <wx/url.h>
#include <wx/wfstream.h>
#include <wx/zipstrm.h>
#include "wayland.h"
#include "strutils.h"
// The built-in xrc file
#include "builtin-xrc.h"
// The built-in vba-over.ini
#include "../common/ConfigManager.h"
#include "builtin-over.h"
IMPLEMENT_APP(wxvbamApp)
IMPLEMENT_DYNAMIC_CLASS(MainFrame, wxFrame)
// generate config file path
static void get_config_path(wxPathList& path, bool exists = true)
{
wxString current_app_name = wxGetApp().GetAppName();
// local config dir first, then global
// locale-specific res first, then main
wxStandardPathsBase& stdp = wxStandardPaths::Get();
#define add_path(p) \
do { \
const wxString& s = stdp.p; \
wxFileName parent = wxFileName::DirName(s + wxT("//..")); \
parent.MakeAbsolute(); \
if ((wxDirExists(s) && wxIsWritable(s)) || ((!exists || !wxDirExists(s)) && parent.IsDirWritable())) \
path.Add(s); \
} while (0)
#define add_nonstandard_path(p) \
do { \
const wxString& s = p; \
wxFileName parent = wxFileName::DirName(s + wxT("//..")); \
parent.MakeAbsolute(); \
if ((wxDirExists(s) && wxIsWritable(s)) || ((!exists || !wxDirExists(s)) && parent.IsDirWritable())) \
path.Add(s); \
} while (0)
static bool debug_dumped = false;
if (!debug_dumped) {
wxLogDebug(wxT("GetUserLocalDataDir(): %s"), stdp.GetUserLocalDataDir().mb_str());
wxLogDebug(wxT("GetUserDataDir(): %s"), stdp.GetUserDataDir().mb_str());
wxLogDebug(wxT("GetLocalizedResourcesDir(wxGetApp().locale.GetCanonicalName()): %s"), stdp.GetLocalizedResourcesDir(wxGetApp().locale.GetCanonicalName()).mb_str());
wxLogDebug(wxT("GetResourcesDir(): %s"), stdp.GetResourcesDir().mb_str());
wxLogDebug(wxT("GetDataDir(): %s"), stdp.GetDataDir().mb_str());
wxLogDebug(wxT("GetLocalDataDir(): %s"), stdp.GetLocalDataDir().mb_str());
wxLogDebug(wxT("plugins_dir: %s"), wxGetApp().GetPluginsDir().mb_str());
wxLogDebug(wxT("XdgConfigDir: %s"), get_xdg_user_config_home() + current_app_name);
debug_dumped = true;
}
// When native support for XDG dirs is available (wxWidgets >= 3.1),
// this will be no longer necessary
#if defined(__WXGTK__)
// XDG spec manual support
// ${XDG_CONFIG_HOME:-$HOME/.config}/`appname`
wxString old_config = wxString(getenv("HOME")) + FILE_SEP + ".vbam";
wxString new_config(get_xdg_user_config_home());
if (!wxDirExists(old_config) && wxIsWritable(new_config))
{
wxFileName new_path(new_config, wxEmptyString);
new_path.AppendDir(current_app_name);
new_path.MakeAbsolute();
add_nonstandard_path(new_path.GetFullPath());
}
else
{
// config is in $HOME/.vbam/
add_nonstandard_path(old_config);
}
#endif
// NOTE: this does not support XDG (freedesktop.org) paths
add_path(GetUserLocalDataDir());
add_path(GetUserDataDir());
add_path(GetLocalizedResourcesDir(wxGetApp().locale.GetCanonicalName()));
add_path(GetResourcesDir());
add_path(GetDataDir());
add_path(GetLocalDataDir());
add_nonstandard_path(wxGetApp().GetPluginsDir());
}
static void tack_full_path(wxString& s, const wxString& app = wxEmptyString)
{
// regenerate path, including nonexistent dirs this time
wxPathList full_config_path;
get_config_path(full_config_path, false);
for (int i = 0; i < full_config_path.size(); i++)
s += wxT("\n\t") + full_config_path[i] + app;
}
const wxString wxvbamApp::GetPluginsDir()
{
return wxStandardPaths::Get().GetPluginsDir();
}
wxString wxvbamApp::GetConfigurationPath()
{
wxString config("vbam.ini");
// first check if config files exists in reverse order
// (from system paths to more local paths.)
if (data_path.empty()) {
get_config_path(config_path);
for (int i = config_path.size() - 1; i >= 0; i--) {
wxFileName fn(config_path[i], config);
if (fn.FileExists() && fn.IsFileWritable()) {
data_path = config_path[i];
break;
}
}
}
// if no config file was not found, search for writable config
// dir or parent to create it in in OnInit in normal order
// (from user paths to system paths.)
if (data_path.empty()) {
for (int i = 0; i < config_path.size(); i++) {
// Check if path is writeable
if (wxIsWritable(config_path[i])) {
data_path = config_path[i];
break;
}
// check if parent of path is writable, so we can
// create the path in OnInit
wxFileName parent_dir = wxFileName::DirName(config_path[i] + wxT("//.."));
parent_dir.MakeAbsolute();
if (parent_dir.IsDirWritable()) {
data_path = config_path[i];
break;
}
}
}
return data_path;
}
wxString wxvbamApp::GetAbsolutePath(wxString path)
{
wxFileName fn(path);
if (fn.IsRelative()) {
fn.MakeRelativeTo(GetConfigurationPath());
fn.Normalize();
return fn.GetFullPath();
}
return path;
}
bool wxvbamApp::OnInit()
{
// set up logging
#ifndef NDEBUG
wxLog::SetLogLevel(wxLOG_Trace);
#endif
// turn off output buffering on Windows to support mintty
#ifdef __WXMSW__
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stderr, NULL, _IONBF, 0);
dup2(1, 2); // redirect stderr to stdout
#endif
using_wayland = IsItWayland();
// use consistent names for config
SetAppName(_("visualboyadvance-m"));
#if (wxMAJOR_VERSION >= 3)
SetAppDisplayName(_T("VisualBoyAdvance-M"));
#endif
// load system default locale, if available
locale.Init();
locale.AddCatalog(_T("wxvbam"));
// make built-in xrc file available
// this has to be done before parent OnInit() so xrc dump works
wxFileSystem::AddHandler(new wxMemoryFSHandler);
wxFileSystem::AddHandler(new wxArchiveFSHandler);
wxMemoryFSHandler::AddFileWithMimeType(wxT("wxvbam.xrs"), builtin_xrs, sizeof(builtin_xrs), wxT("application/zip"));
if (!wxApp::OnInit())
return false;
// prepare for loading xrc files
wxXmlResource* xr = wxXmlResource::Get();
// note: if linking statically, next 2 pull in lot of unused code
// maybe in future if not wxSHARED, load only builtin-needed handlers
xr->InitAllHandlers();
wxInitAllImageHandlers();
get_config_path(config_path);
// first, load override xrcs
// this can only override entire root nodes
// 2.9 has LoadAllFiles(), but this is 2.8, so we'll do it manually
wxString cwd = wxGetCwd();
for (int i = 0; i < config_path.size(); i++)
if (wxDirExists(config_path[i]) && wxSetWorkingDirectory(config_path[i])) {
// *.xr[cs] doesn't work (double the number of scans)
// 2.9 gives errors for no files found, so manual precheck needed
// (yet another double the number of scans)
if (!wxFindFirstFile(wxT("*.xrc")).empty())
xr->Load(wxT("*.xrc"));
if (!wxFindFirstFile(wxT("*.xrs")).empty())
xr->Load(wxT("*.xrs"));
}
wxFileName xrcDir(GetConfigurationPath() + wxT("//xrc"), wxEmptyString);
if (xrcDir.DirExists() && wxSetWorkingDirectory(xrcDir.GetFullPath()) && !wxFindFirstFile(wxT("*.xrc")).empty()) {
xr->Load(wxT("*.xrc"));
} else {
// finally, load built-in xrc
xr->Load(wxT("memory:wxvbam.xrs"));
}
wxSetWorkingDirectory(cwd);
// set up config file
// this needs to be in a subdir to support other config as well
// but subdir flag behaves differently 2.8 vs. 2.9. Oh well.
// NOTE: this does not support XDG (freedesktop.org) paths
wxString confname("vbam.ini");
wxFileName vbamconf(GetConfigurationPath(), confname);
// /MIGRATION
// migrate from 'vbam.{cfg,conf}' to 'vbam.ini' to manage a single config
// file for all platforms.
#if !defined(__WXMSW__) && !defined(__APPLE__)
wxString oldConf(GetConfigurationPath() + FILE_SEP + "vbam.conf");
#else
wxString oldConf(GetConfigurationPath() + FILE_SEP + "vbam.cfg");
#endif
wxString newConf(GetConfigurationPath() + FILE_SEP + "vbam.ini");
if (wxFileExists(oldConf))
{
wxRenameFile(oldConf, newConf, false);
}
// /END_MIGRATION
cfg = new wxFileConfig(wxT("vbam"), wxEmptyString,
vbamconf.GetFullPath(),
wxEmptyString, wxCONFIG_USE_LOCAL_FILE);
// set global config for e.g. Windows font mapping
wxFileConfig::Set(cfg);
// yet another bug/deficiency in wxConfig: dirs are not created if needed
// since a default config is always written, dirs are always needed
// Can't figure out statically if using wxFileConfig w/o duplicating wx's
// logic, so do it at run-time
// wxFileConfig *f = wxDynamicCast(cfg, wxFileConfig);
// wxConfigBase does not derive from wxObject!!! so no wxDynamicCast
wxFileConfig* fc = dynamic_cast<wxFileConfig*>(cfg);
if (fc) {
wxFileName s(wxFileConfig::GetLocalFileName(GetAppName()));
// at least up to 2.8.12, GetLocalFileName returns the dir if
// SUBDIR is specified instead of actual file name
// and SUBDIR only affects UNIX
#if defined(__UNIX__) && !wxCHECK_VERSION(2, 9, 0)
s.AppendDir(s.GetFullName());
#endif
// only the path part gets created
// note that 0777 is default (assumes umask will do og-w)
s.Mkdir(0777, wxPATH_MKDIR_FULL);
s = wxFileName::DirName(GetConfigurationPath());
s.Mkdir(0777, wxPATH_MKDIR_FULL);
}
load_opts();
// wxGLCanvas segfaults under wayland
if (UsingWayland() && gopts.render_method == RND_OPENGL) {
gopts.render_method = RND_SIMPLE;
}
// process command-line options
for (int i = 0; i < pending_optset.size(); i++) {
auto parts = str_split(pending_optset[i], wxT('='));
opt_set(parts[0], parts[1]);
}
pending_optset.clear();
wxFileName vba_over(GetConfigurationPath(), wxT("vba-over.ini"));
wxFileName rdb(GetConfigurationPath(), wxT("Nintendo - Game Boy Advance*.dat"));
wxFileName scene_rdb(GetConfigurationPath(), wxT("Nintendo - Game Boy Advance (Scene)*.dat"));
wxFileName nointro_rdb(GetConfigurationPath(), wxT("Official No-Intro Nintendo Gameboy Advance Number (Date).xml"));
wxString f = wxFindFirstFile(nointro_rdb.GetFullPath(), wxFILE);
if (!f.empty() && wxFileName(f).IsFileReadable())
rom_database_nointro = f;
f = wxFindFirstFile(scene_rdb.GetFullPath(), wxFILE);
if (!f.empty() && wxFileName(f).IsFileReadable())
rom_database_scene = f;
f = wxFindFirstFile(rdb.GetFullPath(), wxFILE);
while (!f.empty()) {
if (f == rom_database_scene.GetFullPath()) {
f = wxFindNextFile();
} else if (wxFileName(f).IsFileReadable()) {
rom_database = f;
break;
}
}
// load vba-over.ini
// rather than dealing with wxConfig's broken search path, just use
// the same one that the xrc overrides use
// this also allows us to override a group at a time, add commments, and
// add the file from which the group came
wxMemoryInputStream mis(builtin_over, sizeof(builtin_over));
overrides = new wxFileConfig(mis);
wxRegEx cmtre;
// not the most efficient thing to do: read entire file into a string
// just to parse the comments out
wxString bovs((const char*)builtin_over, wxConvUTF8, sizeof(builtin_over));
bool cont;
wxString s;
long grp_idx;
#define CMT_RE_START wxT("(^|[\n\r])# ?([^\n\r]*)(\r?\n|\r)\\[")
for (cont = overrides->GetFirstGroup(s, grp_idx); cont;
cont = overrides->GetNextGroup(s, grp_idx)) {
// apparently even MacOSX sometimes uses the old \r by itself
wxString cmt(CMT_RE_START);
cmt += s + wxT("\\]");
if (cmtre.Compile(cmt) && cmtre.Matches(bovs))
cmt = cmtre.GetMatch(bovs, 2);
else
cmt = wxEmptyString;
overrides->Write(s + wxT("/comment"), cmt);
}
if (vba_over.FileExists()) {
wxStringOutputStream sos;
wxFileInputStream fis(vba_over.GetFullPath());
// not the most efficient thing to do: read entire file into a string
// just to parse the comments out
fis.Read(sos);
// rather than assuming the file is seekable, use the string we just
// read as an input stream
wxStringInputStream sis(sos.GetString());
wxFileConfig ov(sis);
for (cont = ov.GetFirstGroup(s, grp_idx); cont;
cont = ov.GetNextGroup(s, grp_idx)) {
overrides->DeleteGroup(s);
overrides->SetPath(s);
ov.SetPath(s);
overrides->Write(wxT("path"), GetConfigurationPath());
// apparently even MacOSX sometimes uses \r by itself
wxString cmt(CMT_RE_START);
cmt += s + wxT("\\]");
if (cmtre.Compile(cmt) && cmtre.Matches(sos.GetString()))
cmt = cmtre.GetMatch(sos.GetString(), 2);
else
cmt = wxEmptyString;
overrides->Write(wxT("comment"), cmt);
long ent_idx;
for (cont = ov.GetFirstEntry(s, ent_idx); cont;
cont = ov.GetNextEntry(s, ent_idx))
overrides->Write(s, ov.Read(s, wxEmptyString));
ov.SetPath(wxT("/"));
overrides->SetPath(wxT("/"));
}
}
// create the main window
int x = windowPositionX;
int y = windowPositionY;
int width = windowWidth;
int height = windowHeight;
int isFullscreen = fullScreen;
frame = wxDynamicCast(xr->LoadFrame(NULL, wxT("MainFrame")), MainFrame);
if (!frame) {
wxLogError(_("Could not create main window"));
return false;
}
// Create() cannot be overridden easily
if (!frame->BindControls())
return false;
if (x >= 0 && y >= 0 && width > 0 && height > 0)
frame->SetSize(x, y, width, height);
if (isFullscreen && wxGetApp().pending_load != wxEmptyString)
frame->ShowFullScreen(isFullscreen);
frame->Show(true);
return true;
}
void wxvbamApp::OnInitCmdLine(wxCmdLineParser& cl)
{
wxApp::OnInitCmdLine(cl);
cl.SetLogo(wxT("VisualBoyAdvance-M\n"));
// 2.9 decided to change all of these from wxChar to char for some
// reason
#if wxCHECK_VERSION(2, 9, 0)
// N_(x) returns x
#define t(x) x
#else
// N_(x) returns wxT(x)
#define t(x) wxT(x)
#endif
// while I would rather the long options be translated, there is merit
// to the idea that command-line syntax should not change based on
// locale
static wxCmdLineEntryDesc opttab[] = {
{ wxCMD_LINE_OPTION, NULL, t("save-xrc"),
N_("Save built-in XRC file and exit") },
{ wxCMD_LINE_OPTION, NULL, t("save-over"),
N_("Save built-in vba-over.ini and exit") },
{ wxCMD_LINE_SWITCH, NULL, t("print-cfg-path"),
N_("Print configuration path and exit") },
{ wxCMD_LINE_SWITCH, t("f"), t("fullscreen"),
N_("Start in full-screen mode") },
#if !defined(NO_LINK) && !defined(__WXMSW__)
{ wxCMD_LINE_SWITCH, t("s"), t("delete-shared-state"),
N_("Delete shared link state first, if it exists") },
#endif
// stupid wx cmd line parser doesn't support duplicate options
// { wxCMD_LINE_OPTION, t("o"), t("option"),
// _("Set configuration option; <opt>=<value> or help for list"),
{
wxCMD_LINE_SWITCH, t("o"), t("list-options"),
N_("List all settable options and exit") },
{ wxCMD_LINE_PARAM, NULL, NULL,
N_("ROM file"), wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL },
{ wxCMD_LINE_PARAM, NULL, NULL,
N_("<config>=<value>"), wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_MULTIPLE | wxCMD_LINE_PARAM_OPTIONAL },
{ wxCMD_LINE_NONE }
};
// 2.9 automatically translates desc, but 2.8 doesn't
#if !wxCHECK_VERSION(2, 9, 0)
for (int i = 0; opttab[i].kind != wxCMD_LINE_NONE; i++)
opttab[i].description = wxGetTranslation(opttab[i].description);
#endif
// note that "SetDesc" actually Adds rather than replaces
// so system standard (port-dependent) options are still included:
// -h/--help --verbose --theme --mode
cl.SetDesc(opttab);
}
bool wxvbamApp::OnCmdLineParsed(wxCmdLineParser& cl)
{
if (!wxApp::OnCmdLineParsed(cl))
return false;
wxString s;
if (cl.Found(wxT("save-xrc"), &s)) {
// This was most likely done on a command line, so use
// stderr instead of gui for messages
wxLog::SetActiveTarget(new wxLogStderr);
wxFileSystem fs;
wxFSFile* f = fs.OpenFile(wxT("memory:wxvbam.xrs#zip:wxvbam.xrs$wxvbam.xrc"));
if (!f) {
wxLogError(_("Configuration/build error: can't find built-in xrc"));
return false;
}
wxFileOutputStream os(s);
os.Write(*f->GetStream());
delete f;
wxString lm;
lm.Printf(_("Wrote built-in configuration to %s.\n"
"To override, remove all but changed root node(s). "
"First found root node of correct name in any .xrc or "
".xrs files in following search path overrides built-in:"),
s.mb_str());
tack_full_path(lm);
wxLogMessage(lm);
return false;
}
if (cl.Found(wxT("print-cfg-path"))) {
// This was most likely done on a command line, so use
// stderr instead of gui for messages
wxLog::SetActiveTarget(new wxLogStderr);
wxString lm(_("Configuration is read from, in order:"));
tack_full_path(lm);
wxLogMessage(lm);
return false;
}
if (cl.Found(wxT("save-over"), &s)) {
// This was most likely done on a command line, so use
// stderr instead of gui for messages
wxLog::SetActiveTarget(new wxLogStderr);
wxFileOutputStream os(s);
os.Write(builtin_over, sizeof(builtin_over));
wxString lm;
lm.Printf(_("Wrote built-in override file to %s\n"
"To override, delete all but changed section. First found section is used from search path:"),
s.mb_str());
wxString oi = wxFileName::GetPathSeparator();
oi += wxT("vba-over.ini");
tack_full_path(lm, oi);
lm.append(_("\n\tbuilt-in"));
wxLogMessage(lm);
return false;
}
if (cl.Found(wxT("f"))) {
pending_fullscreen = true;
}
if (cl.Found(wxT("o"))) {
wxPrintf(_("Options set from the command line are saved if any"
" configuration changes are made in the user interface.\n\n"
"For flag options, true and false are specified as 1 and 0, respectively.\n\n"));
for (int i = 0; i < num_opts; i++) {
wxPrintf(wxT("%s (%s"), opts[i].opt,
opts[i].boolopt ? wxT("flag") : opts[i].stropt ? wxT("string") : !opts[i].enumvals.empty() ? opts[i].enumvals : (wxString)(opts[i].intopt ? wxT("int") : opts[i].doubleopt ? wxT("decimal") : wxT("string")));
if (!opts[i].enumvals.empty()) {
const wxString evx = wxGetTranslation(opts[i].enumvals);
if (wxStrcmp(evx, opts[i].enumvals))
wxPrintf(wxT(" = %s"), evx);
}
wxPrintf(wxT(")\n\t%s\n\n"), opts[i].desc);
if (!opts[i].enumvals.empty())
opts[i].enumvals = wxGetTranslation(opts[i].enumvals);
}
wxPrintf(_("The commands available for the Keyboard/* option are:\n\n"));
for (int i = 0; i < ncmds; i++)
wxPrintf(wxT("%s (%s)\n"), cmdtab[i].cmd, cmdtab[i].name);
return false;
}
#if !defined(NO_LINK) && !defined(__WXMSW__)
if (cl.Found(wxT("s"))) {
CleanLocalLink();
}
#endif
int nparm = cl.GetParamCount();
bool complained = false, gotfile = false;
for (int i = 0; i < nparm; i++) {
auto p = cl.GetParam(i);
auto parts = str_split(p, wxT('='));
if (parts.size() > 1) {
opt_set(parts[0], parts[1]);
pending_optset.push_back(p);
}
else {
if (!gotfile) {
pending_load = p;
gotfile = true;
} else {
if (!complained) {
wxFprintf(stderr, _("Bad configuration option or multiple ROM files given:\n"));
wxFprintf(stderr, wxT("%s\n"), pending_load.mb_str());
complained = true;
}
wxFprintf(stderr, wxT("%s\n"), p);
}
}
}
home = strdup(wxString(wxApp::argv[0]).char_str());
SetHome(home);
LoadConfig(); // Parse command line arguments (overrides ini)
ReadOpts(argc, (char**)argv);
return true;
}
wxvbamApp::~wxvbamApp() {
if (home != NULL)
{
free(home);
home = NULL;
}
}
MainFrame::MainFrame()
: wxFrame()
, focused(false)
, paused(false)
, menus_opened(0)
, dialog_opened(0)
{
}
MainFrame::~MainFrame()
{
#ifndef NO_LINK
CloseLink();
#endif
}
BEGIN_EVENT_TABLE(MainFrame, wxFrame)
#include "cmd-evtable.h"
EVT_CONTEXT_MENU(MainFrame::OnMenu)
// this is the main window focus? Or EVT_SET_FOCUS/EVT_KILL_FOCUS?
EVT_ACTIVATE(MainFrame::OnActivate)
// requires DragAcceptFiles(true); even then may not do anything
EVT_DROP_FILES(MainFrame::OnDropFile)
// for window geometry
EVT_MOVE(MainFrame::OnMove)
EVT_SIZE(MainFrame::OnSize)
// pause game if menu pops up
//
// This is a feature most people don't like, and it causes problems with
// keyboard game keys on mac, so we will disable it for now.
//
// On Winodws, there will still be a pause because of how the windows event
// model works, in addition the audio will loop with SDL, so we still pause on
// Windows, TODO: this needs to be fixed properly
//
#ifdef __WXMSW__
EVT_MENU_OPEN(MainFrame::MenuPopped)
EVT_MENU_CLOSE(MainFrame::MenuPopped)
EVT_MENU_HIGHLIGHT_ALL(MainFrame::MenuPopped)
#endif
END_EVENT_TABLE()
void MainFrame::OnActivate(wxActivateEvent& event)
{
focused = event.GetActive();
if (panel && focused)
panel->SetFocus();
if (pauseWhenInactive) {
if (panel && focused) {
panel->Resume();
}
else if (panel && !focused) {
panel->Pause();
}
}
}
void MainFrame::OnDropFile(wxDropFilesEvent& event)
{
wxString* f = event.GetFiles();
// ignore all but last
wxGetApp().pending_load = f[event.GetNumberOfFiles() - 1];
}
void MainFrame::OnMenu(wxContextMenuEvent& event)
{
if (IsFullScreen() && ctx_menu) {
wxPoint p(event.GetPosition());
#if 0 // wx actually recommends ignoring the position
if (p != wxDefaultPosition)
p = ScreenToClient(p);
#endif
PopupMenu(ctx_menu, p);
}
}
void MainFrame::OnMove(wxMoveEvent& event)
{
wxRect pos = GetRect();
int x = pos.GetX(), y = pos.GetY();
if (x >= 0 && y >= 0 && !IsFullScreen())
{
windowPositionX = x;
windowPositionY = y;
update_opts();
}
}
void MainFrame::OnSize(wxSizeEvent& event)
{
wxFrame::OnSize(event);
wxRect pos = GetRect();
int height = pos.GetHeight(), width = pos.GetWidth();
int x = pos.GetX(), y = pos.GetY();
bool isFullscreen = IsFullScreen();
if (height > 0 && width > 0 && !isFullscreen)
{
windowHeight = height;
windowWidth = width;
}
if (x >= 0 && y >= 0 && !isFullscreen)
{
windowPositionX = x;
windowPositionY = y;
}
fullScreen = isFullscreen;
update_opts();
}
wxString MainFrame::GetGamePath(wxString path)
{
wxString game_path = path;
if (game_path.size()) {
game_path = wxGetApp().GetAbsolutePath(game_path);
} else {
game_path = panel->game_dir();
wxFileName::Mkdir(game_path, 0777, wxPATH_MKDIR_FULL);
}
if (!wxFileName::DirExists(game_path))
game_path = wxFileName::GetCwd();
if (!wxIsWritable(game_path))
game_path = wxGetApp().GetConfigurationPath();
return game_path;
}
void MainFrame::SetJoystick()
{
bool anyjoy = false;
joy.Remove();
if (!emulating)
return;
for (int i = 0; i < 4; i++)
for (int j = 0; j < NUM_KEYS; j++) {
wxJoyKeyBinding_v b = gopts.joykey_bindings[i][j];
for (int k = 0; k < b.size(); k++) {
int jn = b[k].joy;
if (jn) {
if (!anyjoy) {
anyjoy = true;
joy.Attach(panel);
}
joy.Add(jn - 1);
}
}
}
}
void MainFrame::enable_menus()
{
for (int i = 0; i < ncmds; i++)
if (cmdtab[i].mask_flags && cmdtab[i].mi)
cmdtab[i].mi->Enable((cmdtab[i].mask_flags & cmd_enable) != 0);
if (cmd_enable & CMDEN_SAVST)
for (int i = 0; i < 10; i++)
if (loadst_mi[i])
loadst_mi[i]->Enable(state_ts[i].IsValid());
}
void MainFrame::SetRecentAccels()
{
for (int i = 0; i < 10; i++) {
wxMenuItem* mi = recent->FindItem(i + wxID_FILE1);
if (!mi)
break;
// if command is 0, there is no accel
DoSetAccel(mi, recent_accel[i].GetCommand() ? &recent_accel[i] : NULL);
}
}
void MainFrame::update_state_ts(bool force)
{
bool any_states = false;
for (int i = 0; i < 10; i++) {
if (force)
state_ts[i] = wxInvalidDateTime;
if (panel->game_type() != IMAGE_UNKNOWN) {
wxString fn;
fn.Printf(SAVESLOT_FMT, panel->game_name().mb_str(), i + 1);
wxFileName fp(panel->state_dir(), fn);
wxDateTime ts; // = wxInvalidDateTime
if (fp.IsFileReadable()) {
ts = fp.GetModificationTime();
any_states = true;
}
// if(ts != state_ts[i] || (force && !ts.IsValid())) {
// to prevent assertions (stupid wx):
if (ts.IsValid() != state_ts[i].IsValid() || (ts.IsValid() && ts != state_ts[i]) || (force && !ts.IsValid())) {
// wx has no easy way of doing the -- bit independent
// of locale
// so use a real date and substitute all digits
wxDateTime fts = ts.IsValid() ? ts : wxDateTime::Now();
wxString df = fts.Format(wxT("0&0 %x %X"));
if (!ts.IsValid())
for (int j = 0; j < df.size(); j++)
if (wxIsdigit(df[j]))
df[j] = wxT('-');
df[0] = i == 9 ? wxT('1') : wxT(' ');
df[2] = wxT('0') + (i + 1) % 10;
if (loadst_mi[i]) {
wxString lab = loadst_mi[i]->GetItemLabel();
size_t tab = lab.find(wxT('\t')), dflen = df.size();
if (tab != wxString::npos)
df.append(lab.substr(tab));
loadst_mi[i]->SetItemLabel(df);
loadst_mi[i]->Enable(ts.IsValid());
if (tab != wxString::npos)
df.resize(dflen);
}
if (savest_mi[i]) {
wxString lab = savest_mi[i]->GetItemLabel();
size_t tab = lab.find(wxT('\t'));
if (tab != wxString::npos)
df.append(lab.substr(tab));
savest_mi[i]->SetItemLabel(df);
}
}
state_ts[i] = ts;
}
}
int cmd_flg = any_states ? CMDEN_SAVST : 0;
if ((cmd_enable & CMDEN_SAVST) != cmd_flg) {
cmd_enable = (cmd_enable & ~CMDEN_SAVST) | cmd_flg;
enable_menus();
}
}
int MainFrame::oldest_state_slot()
{
wxDateTime ot;
int os = -1;
for (int i = 0; i < 10; i++) {
if (!state_ts[i].IsValid())
return i + 1;
if (os < 0 || state_ts[i] < ot) {
os = i;
ot = state_ts[i];
}
}
return os + 1;
}
int MainFrame::newest_state_slot()
{
wxDateTime nt;
int ns = -1;
for (int i = 0; i < 10; i++) {
if (!state_ts[i].IsValid())
continue;
if (ns < 0 || state_ts[i] > nt) {
ns = i;
nt = state_ts[i];
}
}
return ns + 1;
}
// disable emulator loop while menus are popped up
// not sure how to match up w/ down other than counting...
// only msw is guaranteed to only give one up & one down event for entire
// menu browsing
// if there is ever a mismatch, the game will freeze and there is no way
// to detect if it's doing that
// FIXME: this does not work.
// Not all open events are followed by close events.
// Removing the nesting counter may help, but on wxGTK I still get lockups.
void MainFrame::MenuPopped(wxMenuEvent& evt)
{
bool popped = evt.GetEventType() != wxEVT_MENU_CLOSE;
#if 0
if (popped)
++menus_opened;
else
--menus_opened;
if (menus_opened < 0) // how could this ever be???
menus_opened = 0;
#else
if (popped)
menus_opened = 1;
else
menus_opened = 0;
#endif
// workaround for lack of wxGTK mouse motion events: unblank
// pointer when menu popped up
// of course this does not help in finding the menus in the first place
// the user is better off clicking in the window or entering/
// exiting the window (which does generate a mouse event)
// it will auto-hide again once game resumes
if (popped)
panel->ShowPointer();
if (menus_opened)
panel->Pause();
else if (!IsPaused())
panel->Resume();
}
void MainFrame::SetMenusOpened(bool state)
{
if (state) {
menus_opened = 1;
paused = true;
panel->Pause();
}
else {
menus_opened = 0;
paused = false;
pause_next = false;
panel->Resume();
}
}
// ShowModal that also disables emulator loop
// uses dialog_opened as a nesting counter
int MainFrame::ShowModal(wxDialog* dlg)
{
dlg->SetWindowStyle(dlg->GetWindowStyle() | wxCAPTION | wxRESIZE_BORDER);
if (gopts.keep_on_top)
dlg->SetWindowStyle(dlg->GetWindowStyle() | wxSTAY_ON_TOP);
else
dlg->SetWindowStyle(dlg->GetWindowStyle() & ~wxSTAY_ON_TOP);
CheckPointer(dlg);
StartModal();
int ret = dlg->ShowModal();
StopModal();
return ret;
}
void MainFrame::StartModal()
{
// workaround for lack of wxGTK mouse motion events: unblank
// pointer when dialog popped up
// it will auto-hide again once game resumes
panel->ShowPointer();
panel->Pause();
++dialog_opened;
}
void MainFrame::StopModal()
{
if (!dialog_opened) // technically an error in the caller
return;
--dialog_opened;
if (!IsPaused())
panel->Resume();
}
LinkMode MainFrame::GetConfiguredLinkMode()
{
switch (gopts.gba_link_type) {
case 0:
return LINK_DISCONNECTED;
break;
case 1:
if (gopts.link_proto)
return LINK_CABLE_IPC;
else
return LINK_CABLE_SOCKET;
break;
case 2:
if (gopts.link_proto)
return LINK_RFU_IPC;
else
return LINK_RFU_SOCKET;
break;
case 3:
return LINK_GAMECUBE_DOLPHIN;
break;
case 4:
if (gopts.link_proto)
return LINK_GAMEBOY_IPC;
else
return LINK_GAMEBOY_SOCKET;
break;
default:
return LINK_DISCONNECTED;
break;
}
return LINK_DISCONNECTED;
}
void MainFrame::IdentifyRom()
{
if (!panel->rom_name.empty())
return;
panel->rom_name = panel->loaded_game.GetFullName();
wxString name;
wxString scene_rls;
wxString scene_name;
wxString rom_crc32_str;
rom_crc32_str.Printf(wxT("%08X"), panel->rom_crc32);
if (wxGetApp().rom_database_nointro.FileExists()) {
wxFileInputStream input(wxGetApp().rom_database_nointro.GetFullPath());
wxTextInputStream text(input, wxT("\x09"), wxConvUTF8);
while (input.IsOk() && !input.Eof()) {
wxString line = text.ReadLine();
if (line.Contains(wxT("<releaseNumber>"))) {
scene_rls = line.AfterFirst('>').BeforeLast('<');
}
if (line.Contains(wxT("romCRC ")) && line.Contains(rom_crc32_str)) {
panel->rom_scene_rls = scene_rls;
break;
}
}
}
if (wxGetApp().rom_database_scene.FileExists()) {
wxFileInputStream input(wxGetApp().rom_database_scene.GetFullPath());
wxTextInputStream text(input, wxT("\x09"), wxConvUTF8);
while (input.IsOk() && !input.Eof()) {
wxString line = text.ReadLine();
if (line.StartsWith(wxT("\tname"))) {
scene_name = line.AfterLast(' ').BeforeLast('"');
}
if (line.StartsWith(wxT("\trom")) && line.Contains(wxT("crc ") + rom_crc32_str)) {
panel->rom_scene_rls_name = scene_name;
panel->rom_name = scene_name;
break;
}
}
}
if (wxGetApp().rom_database.FileExists()) {
wxFileInputStream input(wxGetApp().rom_database.GetFullPath());
wxTextInputStream text(input, wxT("\x09"), wxConvUTF8);
while (input.IsOk() && !input.Eof()) {
wxString line = text.ReadLine();
if (line.StartsWith(wxT("\tname"))) {
name = line.AfterFirst('"').BeforeLast('"');
}
if (line.StartsWith(wxT("\trom")) && line.Contains(wxT("crc ") + rom_crc32_str)) {
panel->rom_name = name;
break;
}
}
}
}
// global event filter
// apparently required for win32; just setting accel table still misses
// a few keys (e.g. only ctrl-x works for exit, but not esc & ctrl-q;
// ctrl-w does not work for close). It's possible another entity is
// grabbing those keys, but I can't track it down.
// FIXME: move this to MainFrame
//int wxvbamApp::FilterEvent(wxEvent& event)
//{
// //if(frame && frame->IsPaused(true))
// return -1;
//
// if (event.GetEventType() == wxEVT_KEY_DOWN) {
// wxKeyEvent& ke = (wxKeyEvent&)event;
//
// for (int i = 0; i < accels.size(); i++) {
// if (accels[i].GetKeyCode() == ke.GetKeyCode() && accels[i].GetFlags() == ke.GetModifiers()) {
// wxCommandEvent ev(wxEVT_COMMAND_MENU_SELECTED, accels[i].GetCommand());
// ev.SetEventObject(this);
// frame->GetEventHandler()->ProcessEvent(ev);
// return 1;
// }
// }
// }
//
// return -1;
//}