3732 lines
123 KiB
C++
3732 lines
123 KiB
C++
|
|
// initialize menus & dialogs, etc.
|
|
// for most of the prefs dialogs, all code resides here in the form of
|
|
// event handlers & validators
|
|
// other non-viewer dialogs are at least validated enough that they won't crash
|
|
// viewer dialogs are not commonly used, so they are initialized on demand
|
|
|
|
#include "wxvbam.h"
|
|
|
|
#include <algorithm>
|
|
#include <stdexcept>
|
|
#include <typeinfo>
|
|
|
|
#include <wx/checkedlistctrl.h>
|
|
#include <wx/clrpicker.h>
|
|
#include <wx/dir.h>
|
|
#include <wx/filepicker.h>
|
|
#include <wx/progdlg.h>
|
|
#include <wx/spinctrl.h>
|
|
#include <wx/stockitem.h>
|
|
#include <wx/tokenzr.h>
|
|
#include <wx/txtstrm.h>
|
|
#include <wx/wfstream.h>
|
|
|
|
#include "../common/ConfigManager.h"
|
|
#include "../gba/CheatSearch.h"
|
|
|
|
// The program icon, in case it's missing from .xrc (MSW gets it from .rc file)
|
|
#if !defined(__WXMSW__) && !defined(__WXPM__)
|
|
// ImageMagick makes the name wxvbam, but wx expects wxvbam_xpm
|
|
#define wxvbam wxvbam_xpm
|
|
const
|
|
#include "xrc/vbam.xpm"
|
|
#undef wxvbam
|
|
#endif
|
|
|
|
// this is supposed to happen automatically if a parent is marked recursive
|
|
// but some dialogs don't do it (propertydialog?)
|
|
// so go ahead and mark all dialogs for fully recursive validation
|
|
static void
|
|
mark_recursive(wxWindowBase* w)
|
|
{
|
|
w->SetExtraStyle(w->GetExtraStyle() | wxWS_EX_VALIDATE_RECURSIVELY);
|
|
wxWindowList l = w->GetChildren();
|
|
|
|
for (wxWindowList::iterator ch = l.begin(); ch != l.end(); ++ch)
|
|
mark_recursive(*ch);
|
|
}
|
|
|
|
#if (wxMAJOR_VERSION < 3)
|
|
#define GetXRCDialog(n) \
|
|
wxStaticCast(wxGetApp().frame->FindWindow(XRCID(n)), wxDialog)
|
|
#else
|
|
#define GetXRCDialog(n) \
|
|
wxStaticCast(wxGetApp().frame->FindWindowByName(n), wxDialog)
|
|
#endif
|
|
|
|
// Event handlers must be methods of wxEvtHandler-derived objects
|
|
|
|
// manage the network link dialog
|
|
#ifndef NO_LINK
|
|
static class NetLink_t : public wxEvtHandler {
|
|
public:
|
|
wxDialog* dlg;
|
|
int n_players;
|
|
bool server;
|
|
NetLink_t()
|
|
: n_players(2)
|
|
, server(false)
|
|
{
|
|
}
|
|
wxButton* okb;
|
|
void ServerOKButton(wxCommandEvent& ev)
|
|
{
|
|
okb->SetLabel(_("Start!"));
|
|
}
|
|
void ClientOKButton(wxCommandEvent& ev)
|
|
{
|
|
okb->SetLabel(_("Connect"));
|
|
}
|
|
// attached to OK, so skip when OK
|
|
void NetConnect(wxCommandEvent& ev)
|
|
{
|
|
static const int length = 256;
|
|
|
|
if (!dlg->Validate() || !dlg->TransferDataFromWindow())
|
|
return;
|
|
|
|
if (!server) {
|
|
bool valid = SetLinkServerHost(gopts.link_host.mb_str());
|
|
|
|
if (!valid) {
|
|
wxMessageBox(_("You must enter a valid host name"),
|
|
_("Host name invalid"), wxICON_ERROR | wxOK);
|
|
return;
|
|
}
|
|
}
|
|
|
|
linkNumPlayers = n_players;
|
|
update_opts(); // save fast flag and client host
|
|
// Close any previous link
|
|
CloseLink();
|
|
wxString connmsg;
|
|
wxString title;
|
|
SetLinkTimeout(linkTimeout);
|
|
EnableSpeedHacks(linkHacks);
|
|
EnableLinkServer(server, linkNumPlayers - 1);
|
|
|
|
if (server) {
|
|
char host[length];
|
|
GetLinkServerHost(host, length);
|
|
title.Printf(_("Waiting for clients..."));
|
|
connmsg.Printf(_("Server IP address is: %s\n"), wxString(host, wxConvLibc).mb_str());
|
|
} else {
|
|
title.Printf(_("Waiting for connection..."));
|
|
connmsg.Printf(_("Connecting to %s\n"), gopts.link_host.mb_str());
|
|
}
|
|
|
|
// Init link
|
|
MainFrame* mf = wxGetApp().frame;
|
|
ConnectionState state = InitLink(mf->GetConfiguredLinkMode());
|
|
|
|
// Display a progress dialog while the connection is establishing
|
|
if (state == LINK_NEEDS_UPDATE) {
|
|
wxProgressDialog pdlg(title, connmsg,
|
|
100, dlg, wxPD_APP_MODAL | wxPD_CAN_ABORT | wxPD_ELAPSED_TIME);
|
|
|
|
while (state == LINK_NEEDS_UPDATE) {
|
|
// Ask the core for updates
|
|
char message[length];
|
|
state = ConnectLinkUpdate(message, length);
|
|
connmsg = wxString(message, wxConvLibc);
|
|
|
|
// Does the user want to abort?
|
|
if (!pdlg.Pulse(connmsg)) {
|
|
state = LINK_ABORT;
|
|
}
|
|
}
|
|
}
|
|
|
|
// The user canceled the connection attempt
|
|
if (state == LINK_ABORT) {
|
|
CloseLink();
|
|
}
|
|
|
|
// Something failed during init
|
|
if (state == LINK_ERROR) {
|
|
CloseLink();
|
|
wxLogError(_("Error occurred.\nPlease try again."));
|
|
}
|
|
|
|
if (GetLinkMode() != LINK_DISCONNECTED) {
|
|
connmsg.Replace(wxT("\n"), wxT(" "));
|
|
systemScreenMessage(connmsg);
|
|
ev.Skip(); // all OK
|
|
}
|
|
}
|
|
} net_link_handler;
|
|
#endif
|
|
|
|
// manage the cheat list dialog
|
|
static class CheatList_t : public wxEvtHandler {
|
|
public:
|
|
wxDialog* dlg;
|
|
wxCheckedListCtrl* list;
|
|
wxListItem item0, item1;
|
|
int col1minw;
|
|
wxString cheatdir, cheatfn, deffn;
|
|
bool isgb;
|
|
bool* dirty;
|
|
|
|
// add/edit dialog
|
|
wxString ce_desc;
|
|
wxString ce_codes;
|
|
wxChoice* ce_type_ch;
|
|
wxControl* ce_codes_tc;
|
|
int ce_type;
|
|
|
|
void Reload()
|
|
{
|
|
list->DeleteAllItems();
|
|
Reload(0);
|
|
}
|
|
|
|
void Reload(int start)
|
|
{
|
|
if (isgb) {
|
|
for (int i = start; i < gbCheatNumber; i++) {
|
|
item0.SetId(i);
|
|
item0.SetText(wxString(gbCheatList[i].cheatCode, wxConvLibc));
|
|
list->InsertItem(item0);
|
|
item1.SetId(i);
|
|
item1.SetText(wxString(gbCheatList[i].cheatDesc, wxConvUTF8));
|
|
list->SetItem(item1);
|
|
list->Check(i, gbCheatList[i].enabled);
|
|
}
|
|
} else {
|
|
for (int i = start; i < cheatsNumber; i++) {
|
|
item0.SetId(i);
|
|
item0.SetText(wxString(cheatsList[i].codestring, wxConvLibc));
|
|
list->InsertItem(item0);
|
|
item1.SetId(i);
|
|
item1.SetText(wxString(cheatsList[i].desc, wxConvUTF8));
|
|
list->SetItem(item1);
|
|
list->Check(i, cheatsList[i].enabled);
|
|
}
|
|
}
|
|
|
|
AdjustDescWidth();
|
|
}
|
|
|
|
void Tool(wxCommandEvent& ev)
|
|
{
|
|
switch (ev.GetId()) {
|
|
case wxID_OPEN: {
|
|
wxFileDialog subdlg(dlg, _("Select cheat file"), cheatdir, cheatfn,
|
|
_("VBA cheat lists (*.clt)|*.clt|CHT cheat lists (*.cht)|*.cht"),
|
|
wxFD_OPEN | wxFD_FILE_MUST_EXIST);
|
|
int ret = subdlg.ShowModal();
|
|
cheatdir = subdlg.GetDirectory();
|
|
cheatfn = subdlg.GetPath();
|
|
|
|
if (ret != wxID_OK)
|
|
break;
|
|
|
|
bool cld;
|
|
|
|
if (isgb)
|
|
cld = gbCheatsLoadCheatList(cheatfn.mb_fn_str());
|
|
else {
|
|
if (cheatfn.EndsWith(wxT(".clt"))) {
|
|
cld = cheatsLoadCheatList(cheatfn.mb_fn_str());
|
|
|
|
if (cld) {
|
|
*dirty = cheatfn != deffn;
|
|
systemScreenMessage(_("Loaded cheats"));
|
|
} else
|
|
*dirty = true; // attempted load always clears
|
|
} else {
|
|
// cht format
|
|
wxFileInputStream input(cheatfn);
|
|
wxTextInputStream text(input, wxT("\x09"), wxConvUTF8);
|
|
wxString cheat_desc = wxT("");
|
|
|
|
while (input.IsOk() && !input.Eof()) {
|
|
wxString line = text.ReadLine().Trim();
|
|
|
|
if (line.Contains(wxT("[")) && !line.Contains(wxT("="))) {
|
|
cheat_desc = line.AfterFirst('[').BeforeLast(']');
|
|
}
|
|
|
|
if (line.Contains(wxT("=")) && cheat_desc != wxT("GameInfo")) {
|
|
while ((input.IsOk() && !input.Eof()) && (line.EndsWith(wxT(";")) || line.EndsWith(wxT(",")))) {
|
|
line = line + text.ReadLine().Trim();
|
|
}
|
|
|
|
ParseChtLine(cheat_desc, line);
|
|
}
|
|
}
|
|
|
|
*dirty = true;
|
|
}
|
|
}
|
|
|
|
Reload();
|
|
} break;
|
|
|
|
case wxID_SAVE: {
|
|
wxFileDialog subdlg(dlg, _("Select cheat file"), cheatdir,
|
|
cheatfn, _("VBA cheat lists (*.clt)|*.clt"),
|
|
wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
|
|
int ret = subdlg.ShowModal();
|
|
cheatdir = subdlg.GetDirectory();
|
|
cheatfn = subdlg.GetPath();
|
|
|
|
if (ret != wxID_OK)
|
|
break;
|
|
|
|
// note that there is no way to test for succes of save
|
|
if (isgb)
|
|
gbCheatsSaveCheatList(cheatfn.mb_fn_str());
|
|
else
|
|
cheatsSaveCheatList(cheatfn.mb_fn_str());
|
|
|
|
if (cheatfn == deffn)
|
|
*dirty = false;
|
|
|
|
systemScreenMessage(_("Saved cheats"));
|
|
} break;
|
|
|
|
case wxID_ADD: {
|
|
int ncheats = isgb ? gbCheatNumber : cheatsNumber;
|
|
ce_codes = wxEmptyString;
|
|
wxDialog* subdlg = GetXRCDialog("CheatEdit");
|
|
dlg->SetWindowStyle(wxCAPTION | wxRESIZE_BORDER);
|
|
|
|
if (gopts.keep_on_top)
|
|
subdlg->SetWindowStyle(subdlg->GetWindowStyle() | wxSTAY_ON_TOP);
|
|
else
|
|
subdlg->SetWindowStyle(subdlg->GetWindowStyle() & ~wxSTAY_ON_TOP);
|
|
|
|
subdlg->ShowModal();
|
|
AddCheat();
|
|
Reload(ncheats);
|
|
} break;
|
|
|
|
case wxID_REMOVE: {
|
|
bool asked = false, restore;
|
|
|
|
for (int i = list->GetItemCount() - 1; i >= 0; i--)
|
|
if (list->GetItemState(i, wxLIST_STATE_SELECTED)) {
|
|
list->DeleteItem(i);
|
|
|
|
if (isgb)
|
|
gbCheatRemove(i);
|
|
else {
|
|
if (!asked) {
|
|
asked = true;
|
|
restore = wxMessageBox(_("Restore old values?"),
|
|
_("Removing cheats"),
|
|
wxYES_NO | wxICON_QUESTION)
|
|
== wxYES;
|
|
}
|
|
|
|
cheatsDelete(i, restore);
|
|
}
|
|
}
|
|
} break;
|
|
|
|
case wxID_CLEAR:
|
|
if (isgb) {
|
|
if (gbCheatNumber) {
|
|
*dirty = true;
|
|
gbCheatRemoveAll();
|
|
}
|
|
} else {
|
|
if (cheatsNumber) {
|
|
bool restore = wxMessageBox(_("Restore old values?"),
|
|
_("Removing cheats"),
|
|
wxYES_NO | wxICON_QUESTION)
|
|
== wxYES;
|
|
*dirty = true;
|
|
cheatsDeleteAll(restore);
|
|
}
|
|
}
|
|
|
|
Reload();
|
|
break;
|
|
|
|
case wxID_SELECTALL:
|
|
// FIXME: probably ought to limit to selected items if any items
|
|
// are selected
|
|
*dirty = true;
|
|
|
|
if (isgb) {
|
|
int i;
|
|
|
|
for (i = 0; i < gbCheatNumber; i++)
|
|
if (!gbCheatList[i].enabled)
|
|
break;
|
|
|
|
if (i < gbCheatNumber)
|
|
for (; i < gbCheatNumber; i++) {
|
|
gbCheatEnable(i);
|
|
list->Check(i, true);
|
|
}
|
|
else
|
|
for (i = 0; i < gbCheatNumber; i++) {
|
|
gbCheatDisable(i);
|
|
list->Check(i, false);
|
|
}
|
|
} else {
|
|
int i;
|
|
|
|
for (i = 0; i < cheatsNumber; i++)
|
|
if (!cheatsList[i].enabled)
|
|
break;
|
|
|
|
if (i < cheatsNumber)
|
|
for (; i < cheatsNumber; i++) {
|
|
cheatsEnable(i);
|
|
list->Check(i, true);
|
|
}
|
|
else
|
|
for (i = 0; i < cheatsNumber; i++) {
|
|
cheatsDisable(i);
|
|
list->Check(i, false);
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Check(wxListEvent& ev)
|
|
{
|
|
int ch = ev.GetIndex();
|
|
|
|
if (isgb) {
|
|
if (!gbCheatList[ch].enabled) {
|
|
gbCheatEnable(ev.GetIndex());
|
|
*dirty = true;
|
|
}
|
|
} else {
|
|
if (!cheatsList[ch].enabled) {
|
|
cheatsEnable(ev.GetIndex());
|
|
*dirty = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
void UnCheck(wxListEvent& ev)
|
|
{
|
|
int ch = ev.GetIndex();
|
|
|
|
if (isgb) {
|
|
if (gbCheatList[ch].enabled) {
|
|
gbCheatDisable(ev.GetIndex());
|
|
*dirty = true;
|
|
}
|
|
} else {
|
|
if (cheatsList[ch].enabled) {
|
|
cheatsDisable(ev.GetIndex());
|
|
*dirty = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
void AddCheat()
|
|
{
|
|
wxStringTokenizer tk(ce_codes.MakeUpper());
|
|
|
|
while (tk.HasMoreTokens()) {
|
|
wxString tok = tk.GetNextToken();
|
|
|
|
if (isgb) {
|
|
if (!ce_type)
|
|
gbAddGsCheat(tok.mb_str(), ce_desc.mb_str());
|
|
else
|
|
gbAddGgCheat(tok.mb_str(), ce_desc.mb_str());
|
|
} else {
|
|
// Flashcart CHT format
|
|
if (tok.Contains(wxT("="))) {
|
|
ParseChtLine(ce_desc, tok);
|
|
}
|
|
// Generic Code
|
|
else if (tok.Contains(wxT(":")))
|
|
cheatsAddCheatCode(tok.mb_str(), ce_desc.mb_str());
|
|
// following determination of type by lengths is
|
|
// same used by win32 and gtk code
|
|
// and like win32/gtk code, user-chosen fmt is ignored
|
|
else if (tok.size() == 12) {
|
|
tok = tok.substr(0, 8) + wxT(' ') + tok.substr(8);
|
|
cheatsAddCBACode(tok.mb_str(), ce_desc.mb_str());
|
|
} else if (tok.size() == 16)
|
|
// not sure why 1-tok is !v3 and 2-tok is v3..
|
|
cheatsAddGSACode(tok.mb_str(), ce_desc.mb_str(), false);
|
|
// CBA codes are assumed to be N+4, and anything else
|
|
// is assumed to be GSA v3 (although I assume the
|
|
// actual formats should be 8+4 and 8+8)
|
|
else {
|
|
if (!tk.HasMoreTokens()) {
|
|
// throw an error appropriate to chosen type
|
|
if (ce_type == 1) // GSA
|
|
cheatsAddGSACode(tok.mb_str(), ce_desc.mb_str(), false);
|
|
else
|
|
cheatsAddCBACode(tok.mb_str(), ce_desc.mb_str());
|
|
} else {
|
|
wxString tok2 = tk.GetNextToken();
|
|
|
|
if (tok2.size() == 4) {
|
|
tok += wxT(' ') + tok2;
|
|
cheatsAddCBACode(tok.mb_str(), ce_desc.mb_str());
|
|
} else {
|
|
tok += tok2;
|
|
cheatsAddGSACode(tok.mb_str(), ce_desc.mb_str(), true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ParseChtLine(wxString desc, wxString tok);
|
|
|
|
void Edit(wxListEvent& ev)
|
|
{
|
|
int id = ev.GetIndex();
|
|
// GetItem() followed by GetText doesn't work, so retrieve from
|
|
// source
|
|
wxString odesc, ocode;
|
|
bool ochecked;
|
|
int otype;
|
|
|
|
if (isgb) {
|
|
ochecked = gbCheatList[id].enabled;
|
|
ce_codes = ocode = wxString(gbCheatList[id].cheatCode, wxConvLibc);
|
|
ce_desc = odesc = wxString(gbCheatList[id].cheatDesc, wxConvUTF8);
|
|
|
|
if (ce_codes.find(wxT('-')) == wxString::npos)
|
|
otype = ce_type = 0;
|
|
else
|
|
otype = ce_type = 1;
|
|
} else {
|
|
ochecked = cheatsList[id].enabled;
|
|
ce_codes = ocode = wxString(cheatsList[id].codestring, wxConvLibc);
|
|
ce_desc = odesc = wxString(cheatsList[id].desc, wxConvUTF8);
|
|
|
|
if (ce_codes.find(wxT(':')) != wxString::npos)
|
|
otype = ce_type = 0;
|
|
else if (ce_codes.find(wxT(' ')) == wxString::npos)
|
|
otype = ce_type = 1;
|
|
else
|
|
otype = ce_type = 2;
|
|
}
|
|
|
|
wxDialog* subdlg = GetXRCDialog("CheatEdit");
|
|
dlg->SetWindowStyle(wxCAPTION | wxRESIZE_BORDER);
|
|
|
|
if (gopts.keep_on_top)
|
|
subdlg->SetWindowStyle(subdlg->GetWindowStyle() | wxSTAY_ON_TOP);
|
|
else
|
|
subdlg->SetWindowStyle(subdlg->GetWindowStyle() & ~wxSTAY_ON_TOP);
|
|
|
|
if (subdlg->ShowModal() != wxID_OK)
|
|
return;
|
|
|
|
if (otype != ce_type || ocode != ce_codes) {
|
|
// vba core certainly doesn't make this easy
|
|
// there is no "change" function, so the only way to retain
|
|
// the old order is to delete this and all subsequent items, and
|
|
// then re-add them
|
|
// The MFC code got around this by not even supporting edits on
|
|
// gba codes (which have order dependencies) and just forcing
|
|
// edited codes to the rear on gb codes.
|
|
// It might be safest to only support desc edits, and force the
|
|
// user to re-enter codes to change them
|
|
int ncodes = isgb ? gbCheatNumber : cheatsNumber;
|
|
|
|
if (ncodes > id + 1) {
|
|
wxString codes[MAX_CHEATS];
|
|
wxString descs[MAX_CHEATS];
|
|
bool checked[MAX_CHEATS];
|
|
bool v3[MAX_CHEATS];
|
|
|
|
for (int i = id + 1; i < ncodes; i++) {
|
|
codes[i - id - 1] = wxString(isgb ? gbCheatList[i].cheatCode : cheatsList[i].codestring,
|
|
wxConvLibc);
|
|
descs[i - id - 1] = wxString(isgb ? gbCheatList[i].cheatDesc : cheatsList[i].desc,
|
|
wxConvUTF8);
|
|
checked[i - id - 1] = isgb ? gbCheatList[i].enabled : cheatsList[i].enabled;
|
|
v3[i - id - 1] = isgb ? false : cheatsList[i].code == 257;
|
|
}
|
|
|
|
for (int i = ncodes - 1; i >= id; i--) {
|
|
list->DeleteItem(i);
|
|
|
|
if (isgb)
|
|
gbCheatRemove(i);
|
|
else
|
|
cheatsDelete(i, cheatsList[i].enabled);
|
|
}
|
|
|
|
AddCheat();
|
|
|
|
if (!ochecked) {
|
|
if (isgb)
|
|
gbCheatDisable(id);
|
|
else
|
|
cheatsDisable(id);
|
|
}
|
|
|
|
for (int i = id + 1; i < ncodes; i++) {
|
|
ce_codes = codes[i - id - 1];
|
|
ce_desc = descs[i - id - 1];
|
|
|
|
if (isgb) {
|
|
if (ce_codes.find(wxT('-')) == wxString::npos)
|
|
ce_type = 0;
|
|
else
|
|
ce_type = 1;
|
|
} else {
|
|
if (ce_codes.find(wxT(':')) != wxString::npos)
|
|
ce_type = 0;
|
|
else if (ce_codes.find(wxT(' ')) == wxString::npos) {
|
|
ce_type = 1;
|
|
|
|
if (v3[i - id - 1])
|
|
ce_codes.insert(8, 1, wxT(' '));
|
|
} else {
|
|
ce_type = 2;
|
|
}
|
|
}
|
|
|
|
AddCheat();
|
|
|
|
if (!checked[i - id - 1]) {
|
|
if (isgb)
|
|
gbCheatDisable(i);
|
|
else
|
|
cheatsDisable(i);
|
|
}
|
|
}
|
|
} else {
|
|
list->DeleteItem(id);
|
|
|
|
if (isgb)
|
|
gbCheatRemove(id);
|
|
else
|
|
cheatsDelete(id, cheatsList[id].enabled);
|
|
|
|
AddCheat();
|
|
|
|
if (!ochecked) {
|
|
if (isgb)
|
|
gbCheatDisable(id);
|
|
else
|
|
cheatsDisable(id);
|
|
}
|
|
}
|
|
|
|
Reload(id);
|
|
} else if (ce_desc != odesc) {
|
|
*dirty = true;
|
|
char* p = isgb ? gbCheatList[id].cheatDesc : cheatsList[id].desc;
|
|
strncpy(p, ce_desc.mb_str(), sizeof(cheatsList[0].desc));
|
|
p[sizeof(cheatsList[0].desc) - 1] = 0;
|
|
item1.SetId(id);
|
|
item1.SetText(wxString(p, wxConvUTF8));
|
|
list->SetItem(item1);
|
|
}
|
|
}
|
|
|
|
void AdjustDescWidth()
|
|
{
|
|
// why is it so hard to get an accurate measurement out of wx?
|
|
// on msw, wxLIST_AUTOSIZE might actually be accurate. On wxGTK,
|
|
// and probably wxMAC (both of which use generic impl) wrong
|
|
// font is used both for rendering (col 0's font) and for
|
|
// wxLIST_AUTOSIZE calculation (the widget's font).
|
|
// The only way to defeat this is to calculate size manually
|
|
// Instead, this just allows user to set max size, and retains
|
|
// it.
|
|
int ow = list->GetColumnWidth(1);
|
|
list->SetColumnWidth(1, wxLIST_AUTOSIZE);
|
|
int cw = list->GetColumnWidth(1);
|
|
// subtracted in renderer from width avail for text
|
|
// but not added in wxLIST_AUTOSIZE
|
|
cw += 8;
|
|
|
|
if (cw < col1minw)
|
|
cw = col1minw;
|
|
|
|
if (cw < ow)
|
|
cw = ow;
|
|
|
|
list->SetColumnWidth(1, cw);
|
|
}
|
|
} cheat_list_handler;
|
|
|
|
void CheatList_t::ParseChtLine(wxString desc, wxString tok)
|
|
{
|
|
wxString cheat_opt = tok.BeforeFirst(wxT('='));
|
|
wxString cheat_set = tok.AfterFirst(wxT('='));
|
|
wxStringTokenizer addr_tk(cheat_set.MakeUpper(), wxT(";"));
|
|
|
|
while (addr_tk.HasMoreTokens()) {
|
|
wxString addr_token = addr_tk.GetNextToken();
|
|
wxString cheat_addr = addr_token.BeforeFirst(wxT(','));
|
|
wxString values = addr_token.AfterFirst(wxT(','));
|
|
wxString cheat_desc = desc + wxT(":") + cheat_opt;
|
|
wxString cheat_line;
|
|
wxString cheat_value;
|
|
uint32_t address = 0;
|
|
uint32_t value = 0;
|
|
sscanf(cheat_addr.mb_str(), "%8x", &address);
|
|
|
|
if (address < 0x40000)
|
|
address += 0x2000000;
|
|
else
|
|
address += 0x3000000 - 0x40000;
|
|
|
|
wxStringTokenizer value_tk(values.MakeUpper(), wxT(","));
|
|
|
|
while (value_tk.HasMoreTokens()) {
|
|
wxString value_token = value_tk.GetNextToken();
|
|
sscanf(value_token.mb_str(), "%2x", &value);
|
|
cheat_line.Printf(wxT("%08X"), address);
|
|
cheat_value.Printf(wxT("%02X"), value);
|
|
cheat_line = cheat_line + wxT(":") + cheat_value;
|
|
cheatsAddCheatCode(cheat_line.mb_str(), cheat_desc.mb_str());
|
|
address++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// onshow handler for above, in the form of an overzealous validator
|
|
class CheatListFill : public wxValidator {
|
|
public:
|
|
CheatListFill()
|
|
: wxValidator()
|
|
{
|
|
}
|
|
CheatListFill(const CheatListFill& e)
|
|
: wxValidator()
|
|
{
|
|
}
|
|
wxObject* Clone() const { return new CheatListFill(*this); }
|
|
bool TransferFromWindow() { return true; }
|
|
bool Validate(wxWindow* p) { return true; }
|
|
bool TransferToWindow()
|
|
{
|
|
CheatList_t& clh = cheat_list_handler;
|
|
GameArea* panel = wxGetApp().frame->GetPanel();
|
|
clh.isgb = panel->game_type() == IMAGE_GB;
|
|
clh.dirty = &panel->cheats_dirty;
|
|
clh.cheatfn = panel->game_name() + wxT(".clt");
|
|
clh.cheatdir = panel->game_dir();
|
|
clh.deffn = wxFileName(clh.cheatdir, clh.cheatfn).GetFullPath();
|
|
clh.Reload();
|
|
clh.ce_desc = wxEmptyString;
|
|
wxChoice* ch = clh.ce_type_ch;
|
|
ch->Clear();
|
|
|
|
if (clh.isgb) {
|
|
ch->Append(_("GameShark"));
|
|
ch->Append(_("GameGenie"));
|
|
} else {
|
|
ch->Append(_("Generic Code"));
|
|
ch->Append(_("GameShark Advance"));
|
|
ch->Append(_("CodeBreaker Advance"));
|
|
ch->Append(_("Flashcart CHT"));
|
|
}
|
|
|
|
ch->SetSelection(0);
|
|
return true;
|
|
}
|
|
};
|
|
|
|
// manage the cheat search dialog
|
|
enum cf_vfmt {
|
|
CFVFMT_SD,
|
|
CFVFMT_UD,
|
|
CFVFMT_UH
|
|
};
|
|
|
|
// virtual ListCtrl for cheat search results
|
|
class CheatListCtrl : public wxListCtrl {
|
|
public:
|
|
wxArrayInt addrs;
|
|
int cap_size; // size in effect when addrs were generated
|
|
int count8, count16, count32; // number of aligned addresses in addrs
|
|
wxString OnGetItemText(long item, long column) const;
|
|
|
|
DECLARE_DYNAMIC_CLASS(CheatListCtrl)
|
|
};
|
|
|
|
IMPLEMENT_DYNAMIC_CLASS(CheatListCtrl, wxListCtrl);
|
|
|
|
static class CheatFind_t : public wxEvtHandler {
|
|
public:
|
|
wxDialog* dlg;
|
|
int valsrc, size, op, fmt;
|
|
int ofmt, osize;
|
|
wxString val_s;
|
|
wxTextCtrl* val_tc;
|
|
CheatListCtrl* list;
|
|
|
|
// for enable/disable
|
|
wxRadioButton *old_rb, *val_rb;
|
|
wxControl *update_b, *clear_b, *add_b;
|
|
|
|
bool isgb;
|
|
|
|
// add dialog
|
|
wxString ca_desc, ca_val;
|
|
wxTextCtrl* ca_val_tc;
|
|
wxControl *ca_fmt, *ca_addr;
|
|
|
|
CheatFind_t()
|
|
: wxEvtHandler()
|
|
, valsrc(0)
|
|
, size(0)
|
|
, op(0)
|
|
, fmt(0)
|
|
, val_s()
|
|
{
|
|
}
|
|
~CheatFind_t()
|
|
{
|
|
// not that it matters to anyone but mem leak detectors..
|
|
cheatSearchCleanup(&cheatSearchData);
|
|
}
|
|
|
|
void Search(wxCommandEvent& ev)
|
|
{
|
|
dlg->TransferDataFromWindow();
|
|
|
|
if (!valsrc && val_s.empty()) {
|
|
wxLogError(_("Number cannot be empty"));
|
|
return;
|
|
}
|
|
|
|
if (!cheatSearchData.count)
|
|
ResetSearch(ev);
|
|
|
|
if (valsrc)
|
|
cheatSearch(&cheatSearchData, op, size, fmt == CFVFMT_SD);
|
|
else
|
|
cheatSearchValue(&cheatSearchData, op, size, fmt == CFVFMT_SD,
|
|
SignedValue());
|
|
|
|
Deselect();
|
|
list->addrs.clear();
|
|
list->count8 = list->count16 = list->count32 = 0;
|
|
list->cap_size = size;
|
|
|
|
for (int i = 0; i < cheatSearchData.count; i++) {
|
|
CheatSearchBlock* block = &cheatSearchData.blocks[i];
|
|
|
|
for (int j = 0; j < block->size; j += (1 << size)) {
|
|
if (IS_BIT_SET(block->bits, j)) {
|
|
list->addrs.push_back((i << 28) + j);
|
|
|
|
if (!(j & 1))
|
|
list->count16++;
|
|
|
|
if (!(j & 3))
|
|
list->count32++;
|
|
|
|
// since listctrl is virtual, it should be able to handle
|
|
// at least 256k results, which is about the most you
|
|
// will ever get
|
|
#if 0
|
|
|
|
if (list->addrs.size() > 1000)
|
|
{
|
|
wxLogError(_("Search produced %d results. Please refine better"),
|
|
list->addrs.size());
|
|
list->addrs.clear();
|
|
return;
|
|
}
|
|
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
if (list->addrs.empty()) {
|
|
wxLogError(_("Search produced no results"));
|
|
// no point in keeping empty search results around
|
|
ResetSearch(ev);
|
|
|
|
if (old_rb->GetValue()) {
|
|
val_rb->SetValue(true);
|
|
// SetValue doesn't generate an event
|
|
val_tc->Enable();
|
|
}
|
|
|
|
old_rb->Disable();
|
|
update_b->Disable();
|
|
clear_b->Disable();
|
|
} else {
|
|
switch (size) {
|
|
case BITS_32:
|
|
list->count16 = list->count32 * 2;
|
|
|
|
// fall through
|
|
case BITS_16:
|
|
list->count8 = list->count16 * 2;
|
|
break;
|
|
|
|
case BITS_8:
|
|
list->count8 = list->addrs.size();
|
|
}
|
|
|
|
old_rb->Enable();
|
|
update_b->Enable();
|
|
clear_b->Enable();
|
|
}
|
|
|
|
list->SetItemCount(list->addrs.size());
|
|
list->Refresh();
|
|
}
|
|
|
|
void UpdateVals(wxCommandEvent& ev)
|
|
{
|
|
if (cheatSearchData.count) {
|
|
cheatSearchUpdateValues(&cheatSearchData);
|
|
|
|
if (list->count8)
|
|
list->Refresh();
|
|
|
|
update_b->Disable();
|
|
}
|
|
}
|
|
|
|
void ResetSearch(wxCommandEvent& ev)
|
|
{
|
|
if (!cheatSearchData.count) {
|
|
CheatSearchBlock* block = cheatSearchData.blocks;
|
|
|
|
if (isgb) {
|
|
block->offset = 0xa000;
|
|
|
|
if (gbRam)
|
|
block->data = gbRam;
|
|
else
|
|
block->data = &gbMemory[0xa000];
|
|
|
|
block->saved = (uint8_t*)malloc(gbRamSize);
|
|
block->size = gbRamSize;
|
|
block->bits = (uint8_t*)malloc(gbRamSize >> 3);
|
|
|
|
if (gbCgbMode) {
|
|
block++;
|
|
block->offset = 0xc000;
|
|
block->data = &gbMemory[0xc000];
|
|
block->saved = (uint8_t*)malloc(0x1000);
|
|
block->size = 0x1000;
|
|
block->bits = (uint8_t*)malloc(0x1000 >> 3);
|
|
block++;
|
|
block->offset = 0xd000;
|
|
block->data = gbWram;
|
|
block->saved = (uint8_t*)malloc(0x8000);
|
|
block->size = 0x8000;
|
|
block->bits = (uint8_t*)malloc(0x8000 >> 3);
|
|
} else {
|
|
block++;
|
|
block->offset = 0xc000;
|
|
block->data = &gbMemory[0xc000];
|
|
block->saved = (uint8_t*)malloc(0x2000);
|
|
block->size = 0x2000;
|
|
block->bits = (uint8_t*)malloc(0x2000 >> 3);
|
|
}
|
|
} else {
|
|
block->size = 0x40000;
|
|
block->offset = 0x2000000;
|
|
block->bits = (uint8_t*)malloc(0x40000 >> 3);
|
|
block->data = workRAM;
|
|
block->saved = (uint8_t*)malloc(0x40000);
|
|
block++;
|
|
block->size = 0x8000;
|
|
block->offset = 0x3000000;
|
|
block->bits = (uint8_t*)malloc(0x8000 >> 3);
|
|
block->data = internalRAM;
|
|
block->saved = (uint8_t*)malloc(0x8000);
|
|
}
|
|
|
|
cheatSearchData.count = (int)((block + 1) - cheatSearchData.blocks);
|
|
}
|
|
|
|
cheatSearchStart(&cheatSearchData);
|
|
|
|
if (list->count8) {
|
|
Deselect();
|
|
list->count8 = list->count16 = list->count32 = 0;
|
|
list->addrs.clear();
|
|
list->SetItemCount(0);
|
|
list->Refresh();
|
|
|
|
if (old_rb->GetValue()) {
|
|
val_rb->SetValue(true);
|
|
// SetValue doesn't generate an event
|
|
val_tc->Enable();
|
|
}
|
|
|
|
old_rb->Disable();
|
|
update_b->Disable();
|
|
clear_b->Disable();
|
|
}
|
|
}
|
|
|
|
void Deselect()
|
|
{
|
|
int idx = list->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
|
|
|
|
if (idx >= 0)
|
|
list->SetItemState(idx, 0, wxLIST_STATE_SELECTED);
|
|
|
|
add_b->Disable();
|
|
}
|
|
|
|
void Select(wxListEvent& ev)
|
|
{
|
|
add_b->Enable(list->GetItemState(ev.GetIndex(), wxLIST_STATE_SELECTED) != 0);
|
|
}
|
|
|
|
void AddCheatB(wxCommandEvent& ev)
|
|
{
|
|
int idx = list->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
|
|
|
|
if (idx >= 0)
|
|
AddCheat(idx);
|
|
}
|
|
|
|
void AddCheatL(wxListEvent& ev)
|
|
{
|
|
AddCheat(ev.GetIndex());
|
|
}
|
|
|
|
void AddCheat(int idx)
|
|
{
|
|
wxString addr_s = list->OnGetItemText(idx, 0);
|
|
ca_addr->SetLabel(addr_s);
|
|
wxString s;
|
|
|
|
switch (size) {
|
|
case BITS_8:
|
|
s = _("8-bit ");
|
|
break;
|
|
|
|
case BITS_16:
|
|
s = _("16-bit ");
|
|
break;
|
|
|
|
case BITS_32:
|
|
s = _("32-bit ");
|
|
break;
|
|
}
|
|
|
|
switch (fmt) {
|
|
case CFVFMT_SD:
|
|
s += _("signed decimal");
|
|
break;
|
|
|
|
case CFVFMT_UD:
|
|
s += _("unsigned decimal");
|
|
break;
|
|
|
|
case CFVFMT_UH:
|
|
s += _("unsigned hexadecimal");
|
|
break;
|
|
}
|
|
|
|
ca_fmt->SetLabel(s);
|
|
// probably pointless (but inoffensive) to suggest a value
|
|
ca_val = list->OnGetItemText(idx, 2); // sugest "New" value
|
|
SetValVal(ca_val_tc);
|
|
wxDialog* subdlg = GetXRCDialog("CheatAdd");
|
|
dlg->SetWindowStyle(wxCAPTION | wxRESIZE_BORDER);
|
|
|
|
if (gopts.keep_on_top)
|
|
subdlg->SetWindowStyle(subdlg->GetWindowStyle() | wxSTAY_ON_TOP);
|
|
else
|
|
subdlg->SetWindowStyle(subdlg->GetWindowStyle() & ~wxSTAY_ON_TOP);
|
|
|
|
if (subdlg->ShowModal() != wxID_OK)
|
|
return;
|
|
|
|
if (ca_val.empty()) {
|
|
wxLogError(_("Number cannot be empty"));
|
|
return;
|
|
}
|
|
|
|
uint32_t val = GetValue(ca_val, fmt);
|
|
|
|
if (isgb) {
|
|
long bank, addr;
|
|
addr_s.ToLong(&bank, 16);
|
|
addr_s.erase(0, 3);
|
|
addr_s.ToLong(&addr, 16);
|
|
|
|
if (addr >= 0xd000)
|
|
bank += 0x90;
|
|
else
|
|
bank = 1;
|
|
|
|
for (int i = 0; i < (1 << size); i++) {
|
|
addr_s.Printf(wxT("%02X%02X%02X%02X"), bank, val & 0xff,
|
|
addr & 0xff, addr >> 8);
|
|
gbAddGsCheat(addr_s.mb_str(), ca_desc.mb_str());
|
|
val >>= 8;
|
|
addr++;
|
|
}
|
|
} else {
|
|
wxString s;
|
|
|
|
switch (size) {
|
|
case BITS_8:
|
|
s.Printf(wxT(":%02X"), val);
|
|
break;
|
|
|
|
case BITS_16:
|
|
s.Printf(wxT(":%04X"), val);
|
|
break;
|
|
|
|
case BITS_32:
|
|
s.Printf(wxT(":%08X"), val);
|
|
break;
|
|
}
|
|
|
|
addr_s.append(s);
|
|
cheatsAddCheatCode(addr_s.mb_str(), ca_desc.mb_str());
|
|
}
|
|
}
|
|
|
|
void SetValVal(wxTextCtrl* tc)
|
|
{
|
|
wxTextValidator* v = wxStaticCast(tc->GetValidator(), wxTextValidator);
|
|
|
|
switch (fmt) {
|
|
case CFVFMT_SD:
|
|
v->SetIncludes(val_sigdigits);
|
|
break;
|
|
|
|
case CFVFMT_UD:
|
|
v->SetIncludes(val_unsdigits);
|
|
break;
|
|
|
|
case CFVFMT_UH:
|
|
v->SetIncludes(val_hexdigits);
|
|
break;
|
|
}
|
|
}
|
|
|
|
uint32_t GetValue(wxString& s, int fmt)
|
|
{
|
|
long val;
|
|
// FIXME: probably ought to throw an error if ToLong
|
|
// returns false or val is out of range
|
|
s.ToLong(&val, fmt == CFVFMT_UH ? 16 : 10);
|
|
|
|
if (size != BITS_32)
|
|
val &= size == BITS_8 ? 0xff : 0xffff;
|
|
|
|
return val;
|
|
}
|
|
|
|
uint32_t GetValue(int fmt)
|
|
{
|
|
return GetValue(val_s, fmt);
|
|
}
|
|
|
|
uint32_t GetValue()
|
|
{
|
|
return GetValue(fmt);
|
|
}
|
|
|
|
int32_t SignedValue(wxString& s, int fmt)
|
|
{
|
|
int32_t val = GetValue(s, fmt);
|
|
|
|
if (fmt == CFVFMT_SD) {
|
|
if (size == BITS_8)
|
|
val = (int32_t)(int8_t)val;
|
|
else if (size == BITS_16)
|
|
val = (int32_t)(int16_t)val;
|
|
}
|
|
|
|
return val;
|
|
}
|
|
|
|
int32_t SignedValue(int fmt)
|
|
{
|
|
return SignedValue(val_s, fmt);
|
|
}
|
|
|
|
int32_t SignedValue()
|
|
{
|
|
return SignedValue(fmt);
|
|
}
|
|
|
|
void FormatValue(int32_t val, wxString& s)
|
|
{
|
|
if (fmt != CFVFMT_SD && size != BITS_32)
|
|
val &= size == BITS_8 ? 0xff : 0xffff;
|
|
|
|
switch (fmt) {
|
|
case CFVFMT_SD:
|
|
s.Printf(wxT("%d"), val);
|
|
break;
|
|
|
|
case CFVFMT_UD:
|
|
s.Printf(wxT("%u"), val);
|
|
break;
|
|
|
|
case CFVFMT_UH:
|
|
switch (size) {
|
|
case BITS_8:
|
|
s.Printf(wxT("%02X"), val);
|
|
break;
|
|
|
|
case BITS_16:
|
|
s.Printf(wxT("%04X"), val);
|
|
break;
|
|
|
|
case BITS_32:
|
|
s.Printf(wxT("%08X"), val);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void UpdateView(wxCommandEvent& ev)
|
|
{
|
|
dlg->TransferDataFromWindow();
|
|
|
|
if (ofmt != fmt && !val_s.empty()) {
|
|
int32_t val = GetValue(ofmt);
|
|
|
|
switch (fmt) {
|
|
case CFVFMT_SD:
|
|
switch (size) {
|
|
case BITS_8:
|
|
val = (int32_t)(int8_t)val;
|
|
break;
|
|
|
|
case BITS_16:
|
|
val = (int32_t)(int16_t)val;
|
|
}
|
|
|
|
val_s.Printf(wxT("%d"), val);
|
|
break;
|
|
|
|
case CFVFMT_UD:
|
|
val_s.Printf(wxT("%u"), val);
|
|
break;
|
|
|
|
case CFVFMT_UH:
|
|
val_s.Printf(wxT("%x"), val);
|
|
break;
|
|
}
|
|
|
|
val_tc->SetValue(val_s);
|
|
}
|
|
|
|
if (ofmt != fmt)
|
|
SetValVal(val_tc);
|
|
|
|
if (list->count8 && osize != size) {
|
|
switch (size) {
|
|
case BITS_32:
|
|
list->SetItemCount(list->count32);
|
|
break;
|
|
|
|
case BITS_16:
|
|
list->SetItemCount(list->count16);
|
|
break;
|
|
|
|
case BITS_8:
|
|
list->SetItemCount(list->count8);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (ofmt != fmt || osize != size)
|
|
list->Refresh();
|
|
|
|
ofmt = fmt;
|
|
osize = size;
|
|
}
|
|
|
|
void EnableVal(wxCommandEvent& ev)
|
|
{
|
|
val_tc->Enable(ev.GetId() == XRCID("SpecificValue"));
|
|
}
|
|
|
|
} cheat_find_handler;
|
|
|
|
// clear cheat find dialog between games
|
|
void MainFrame::ResetCheatSearch()
|
|
{
|
|
CheatFind_t& cfh = cheat_find_handler;
|
|
cfh.fmt = cfh.size = cfh.op = cfh.valsrc = 0;
|
|
cfh.val_s = wxEmptyString;
|
|
cfh.Deselect();
|
|
cfh.list->SetItemCount(0);
|
|
cfh.list->count8 = cfh.list->count16 = cfh.list->count32 = 0;
|
|
cfh.list->addrs.clear();
|
|
cfh.ca_desc = wxEmptyString;
|
|
cheatSearchCleanup(&cheatSearchData);
|
|
}
|
|
|
|
// onshow handler for above, in the form of an overzealous validator
|
|
class CheatFindFill : public wxValidator {
|
|
public:
|
|
CheatFindFill()
|
|
: wxValidator()
|
|
{
|
|
}
|
|
CheatFindFill(const CheatFindFill& e)
|
|
: wxValidator()
|
|
{
|
|
}
|
|
wxObject* Clone() const { return new CheatFindFill(*this); }
|
|
bool TransferFromWindow() { return true; }
|
|
bool Validate(wxWindow* p) { return true; }
|
|
bool TransferToWindow()
|
|
{
|
|
CheatFind_t& cfh = cheat_find_handler;
|
|
GameArea* panel = wxGetApp().frame->GetPanel();
|
|
cfh.isgb = panel->game_type() == IMAGE_GB;
|
|
cfh.val_tc->Enable(!cfh.valsrc);
|
|
cfh.ofmt = cfh.fmt;
|
|
cfh.SetValVal(cfh.val_tc);
|
|
return true;
|
|
}
|
|
};
|
|
|
|
// the implementation of the virtual list ctrl for search results
|
|
// requires CheatFind_t to be implemented
|
|
wxString CheatListCtrl::OnGetItemText(long item, long column) const
|
|
{
|
|
wxString s;
|
|
CheatFind_t& cfh = cheat_find_handler;
|
|
// allowing GUI to change format after search makes this a little
|
|
// more complicated than necessary...
|
|
int off = 0;
|
|
int size = cfh.size;
|
|
|
|
if (cap_size > size) {
|
|
off = (item & ((1 << (cap_size - size)) - 1)) << size;
|
|
item >>= cap_size - size;
|
|
} else if (cap_size < size) {
|
|
for (int i = 0; i < addrs.size(); i++) {
|
|
if (!(addrs[i] & ((1 << size) - 1)) && !item--) {
|
|
item = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
CheatSearchBlock* block = &cheatSearchData.blocks[addrs[item] >> 28];
|
|
off += addrs[item] & 0xfffffff;
|
|
|
|
switch (column) {
|
|
case 0: // address
|
|
if (cfh.isgb) {
|
|
int bank = 0;
|
|
int addr = block->offset;
|
|
|
|
if (block->offset == 0xa000) {
|
|
bank = off / 0x2000;
|
|
addr += off % 0x2000;
|
|
} else if (block->offset == 0xd000) {
|
|
bank = off / 0x1000;
|
|
addr += off % 0x1000;
|
|
} else
|
|
addr += off;
|
|
|
|
s.Printf(wxT("%02X:%04X"), bank, addr);
|
|
} else
|
|
s.Printf(wxT("%08X"), block->offset + off);
|
|
|
|
break;
|
|
|
|
case 1: // old
|
|
cfh.FormatValue(cheatSearchSignedRead(block->saved, off, size), s);
|
|
break;
|
|
|
|
case 2: // new
|
|
cfh.FormatValue(cheatSearchSignedRead(block->data, off, size), s);
|
|
break;
|
|
}
|
|
|
|
return s;
|
|
}
|
|
|
|
// these are the choices for canned colors; their order must match the
|
|
// names in the choice control
|
|
static const uint16_t defaultPalettes[][8] = {
|
|
{
|
|
// Standard
|
|
0x7FFF, 0x56B5, 0x318C, 0x0000, 0x7FFF, 0x56B5, 0x318C, 0x0000,
|
|
},
|
|
{
|
|
// Blue Sea
|
|
0x6200, 0x7E10, 0x7C10, 0x5000, 0x6200, 0x7E10, 0x7C10, 0x5000,
|
|
},
|
|
{
|
|
// Dark Night
|
|
0x4008, 0x4000, 0x2000, 0x2008, 0x4008, 0x4000, 0x2000, 0x2008,
|
|
},
|
|
{
|
|
// Green Forest
|
|
0x43F0, 0x03E0, 0x4200, 0x2200, 0x43F0, 0x03E0, 0x4200, 0x2200,
|
|
},
|
|
{
|
|
// Hot Desert
|
|
0x43FF, 0x03FF, 0x221F, 0x021F, 0x43FF, 0x03FF, 0x221F, 0x021F,
|
|
},
|
|
{
|
|
// Pink Dreams
|
|
0x621F, 0x7E1F, 0x7C1F, 0x2010, 0x621F, 0x7E1F, 0x7C1F, 0x2010,
|
|
},
|
|
{
|
|
// Weird Colors
|
|
0x621F, 0x401F, 0x001F, 0x2010, 0x621F, 0x401F, 0x001F, 0x2010,
|
|
},
|
|
{
|
|
// Real GB Colors
|
|
0x1B8E, 0x02C0, 0x0DA0, 0x1140, 0x1B8E, 0x02C0, 0x0DA0, 0x1140,
|
|
},
|
|
{
|
|
// Real 'GB on GBASP' Colors
|
|
0x7BDE, /*0x23F0*/ 0x5778, /*0x5DC0*/ 0x5640, 0x0000, 0x7BDE, /*0x3678*/ 0x529C, /*0x0980*/ 0x2990, 0x0000,
|
|
}
|
|
};
|
|
|
|
// manage the GB color prefs' canned color selecter
|
|
static class GBColorConfig_t : public wxEvtHandler {
|
|
public:
|
|
wxWindow* p;
|
|
wxChoice* c;
|
|
wxColourPickerCtrl* cp[8];
|
|
int pno;
|
|
void ColorSel(wxCommandEvent& ev)
|
|
{
|
|
if (ev.GetSelection() > 0) {
|
|
const uint16_t* color = defaultPalettes[ev.GetSelection() - 1];
|
|
|
|
for (int i = 0; i < 8; i++, color++)
|
|
cp[i]->SetColour(wxColor(((*color << 3) & 0xf8),
|
|
((*color >> 2) & 0xf8),
|
|
((*color >> 7) & 0xf8)));
|
|
}
|
|
}
|
|
void ColorReset(wxCommandEvent& ev)
|
|
{
|
|
const uint16_t* color = &systemGbPalette[pno * 8];
|
|
|
|
for (int i = 0; i < 8; i++, color++)
|
|
cp[i]->SetColour(wxColor(((*color << 3) & 0xf8),
|
|
((*color >> 2) & 0xf8),
|
|
((*color >> 7) & 0xf8)));
|
|
}
|
|
|
|
void ColorButton(wxCommandEvent& ev)
|
|
{
|
|
c->SetSelection(0);
|
|
}
|
|
} GBColorConfigHandler[3];
|
|
|
|
// disable controls if a GBA game is not loaded
|
|
class GBACtrlEnabler : public wxValidator {
|
|
public:
|
|
GBACtrlEnabler()
|
|
: wxValidator()
|
|
{
|
|
}
|
|
GBACtrlEnabler(const GBACtrlEnabler& e)
|
|
: wxValidator()
|
|
{
|
|
}
|
|
wxObject* Clone() const { return new GBACtrlEnabler(*this); }
|
|
bool TransferFromWindow() { return true; }
|
|
bool Validate(wxWindow* p) { return true; }
|
|
bool TransferToWindow()
|
|
{
|
|
GetWindow()->Enable(wxGetApp().frame->GetPanel()->game_type() == IMAGE_GBA);
|
|
return true;
|
|
}
|
|
};
|
|
|
|
// manage save game area settings for GBA prefs
|
|
static class BatConfig_t : public wxEvtHandler {
|
|
public:
|
|
wxChoice *type, *size;
|
|
void ChangeType(wxCommandEvent& ev)
|
|
{
|
|
int i = ev.GetSelection();
|
|
size->Enable(!i || i == 3); // automatic/flash
|
|
}
|
|
void Detect(wxCommandEvent& ev)
|
|
{
|
|
uint32_t sz = wxGetApp().frame->GetPanel()->game_size();
|
|
utilGBAFindSave(sz);
|
|
type->SetSelection(saveType);
|
|
|
|
if (saveType == 3) {
|
|
size->SetSelection(flashSize == 0x20000 ? 1 : 0);
|
|
size->Enable();
|
|
} else {
|
|
size->Disable();
|
|
}
|
|
}
|
|
} BatConfigHandler;
|
|
|
|
// manage the sound prefs dialog
|
|
static class SoundConfig_t : public wxEvtHandler {
|
|
public:
|
|
wxSlider *vol, *bufs;
|
|
wxControl* bufinfo;
|
|
int lastapi;
|
|
wxChoice* dev;
|
|
wxControl *umix, *hwacc;
|
|
wxArrayString dev_ids;
|
|
|
|
void FullVol(wxCommandEvent& ev)
|
|
{
|
|
vol->SetValue(100);
|
|
}
|
|
void AdjustFrames(int count)
|
|
{
|
|
wxString s;
|
|
s.Printf(_("%d frames = %.2f ms"), count, (double)count / 60.0 * 1000.0);
|
|
bufinfo->SetLabel(s);
|
|
}
|
|
void AdjustFramesEv(wxCommandEvent& ev)
|
|
{
|
|
AdjustFrames(bufs->GetValue());
|
|
}
|
|
|
|
bool FillDev(int api)
|
|
{
|
|
dev->Clear();
|
|
dev->Append(_("Default device"));
|
|
dev_ids.clear();
|
|
wxArrayString names;
|
|
|
|
switch (api) {
|
|
case AUD_SDL:
|
|
break;
|
|
#ifndef NO_OAL
|
|
|
|
case AUD_OPENAL:
|
|
if (!GetOALDevices(names, dev_ids))
|
|
return false;
|
|
|
|
break;
|
|
#endif
|
|
#ifdef __WXMSW__
|
|
|
|
case AUD_DIRECTSOUND:
|
|
if (!(GetDSDevices(names, dev_ids)))
|
|
return false;
|
|
|
|
break;
|
|
#ifndef NO_XAUDIO2
|
|
|
|
case AUD_XAUDIO2:
|
|
if (!GetXA2Devices(names, dev_ids))
|
|
return false;
|
|
|
|
break;
|
|
#endif
|
|
#ifndef NO_FAUDIO
|
|
|
|
case AUD_FAUDIO:
|
|
if (!GetFADevices(names, dev_ids))
|
|
return false;
|
|
|
|
break;
|
|
#endif
|
|
#endif
|
|
}
|
|
|
|
dev->SetSelection(0);
|
|
|
|
for (int i = 0; i < names.size(); i++) {
|
|
dev->Append(names[i]);
|
|
|
|
if (api == gopts.audio_api && gopts.audio_dev == dev_ids[i])
|
|
dev->SetSelection(i + 1);
|
|
}
|
|
|
|
umix->Enable(api == AUD_XAUDIO2);
|
|
hwacc->Enable(api == AUD_DIRECTSOUND);
|
|
lastapi = api;
|
|
return true;
|
|
}
|
|
void SetAPI(wxCommandEvent& ev)
|
|
{
|
|
int api = gopts.audio_api;
|
|
wxValidator* v = wxStaticCast(ev.GetEventObject(), wxWindow)->GetValidator();
|
|
v->TransferFromWindow();
|
|
int newapi = gopts.audio_api;
|
|
gopts.audio_api = api;
|
|
|
|
if (newapi == lastapi)
|
|
return;
|
|
|
|
FillDev(newapi);
|
|
}
|
|
} sound_config_handler;
|
|
|
|
// Validator/widget filler for sound device selector & time indicator
|
|
class SoundConfigLoad : public wxValidator {
|
|
public:
|
|
SoundConfigLoad()
|
|
: wxValidator()
|
|
{
|
|
}
|
|
SoundConfigLoad(const SoundConfigLoad& e)
|
|
: wxValidator()
|
|
{
|
|
}
|
|
wxObject* Clone() const { return new SoundConfigLoad(*this); }
|
|
bool Validate(wxWindow* p) { return true; }
|
|
bool TransferToWindow()
|
|
{
|
|
SoundConfig_t& sch = sound_config_handler;
|
|
sch.FillDev(gopts.audio_api);
|
|
sch.AdjustFrames(gopts.audio_buffers);
|
|
return true;
|
|
}
|
|
bool TransferFromWindow()
|
|
{
|
|
SoundConfig_t& sch = sound_config_handler;
|
|
int devs = sch.dev->GetSelection();
|
|
|
|
if (devs <= 0)
|
|
gopts.audio_dev = wxEmptyString;
|
|
else
|
|
gopts.audio_dev = sch.dev_ids[devs - 1];
|
|
|
|
return true;
|
|
}
|
|
};
|
|
|
|
// manage the joypad prefs' per-panel default/clear buttons
|
|
static class JoyPadConfig_t : public wxEvtHandler {
|
|
public:
|
|
wxWindow* p;
|
|
void JoypadConfigButtons(wxCommandEvent& ev)
|
|
{
|
|
bool clear = ev.GetId() == XRCID("Clear");
|
|
|
|
for (int i = 0; i < NUM_KEYS; i++) {
|
|
wxJoyKeyTextCtrl* tc = XRCCTRL_D(*p, joynames[i], wxJoyKeyTextCtrl);
|
|
|
|
if (clear)
|
|
tc->SetValue(wxEmptyString);
|
|
else {
|
|
wxJoyKeyBinding_v a;
|
|
|
|
if (defkeys[i * 2].key)
|
|
a.push_back(defkeys[i * 2]);
|
|
|
|
if (defkeys[i * 2 + 1].joy)
|
|
a.push_back(defkeys[i * 2 + 1]);
|
|
|
|
tc->SetValue(wxJoyKeyTextCtrl::ToString(a));
|
|
}
|
|
}
|
|
}
|
|
} JoyPadConfigHandler[4];
|
|
|
|
// manage fullscreen mode widget
|
|
// technically, it's more than a validator: it modifies the widget as well
|
|
class ScreenModeList : public wxValidator {
|
|
public:
|
|
ScreenModeList()
|
|
: wxValidator()
|
|
{
|
|
}
|
|
ScreenModeList(const ScreenModeList& e)
|
|
: wxValidator()
|
|
{
|
|
}
|
|
wxObject* Clone() const { return new ScreenModeList(*this); }
|
|
bool Validate(wxWindow* p) { return true; }
|
|
bool TransferToWindow()
|
|
{
|
|
wxChoice* c = wxStaticCast(GetWindow(), wxChoice);
|
|
wxDisplay d(wxDisplay::GetFromWindow(c->GetParent()));
|
|
c->Clear();
|
|
int modeno = 0, bestmode = 0;
|
|
int bm_bpp = 0;
|
|
c->Append(_("Desktop mode"));
|
|
// probably ought to just disable this whole control on UNIX/X11 since
|
|
// wxDisplay is so broken.
|
|
vm = d.GetModes();
|
|
wxString s;
|
|
|
|
for (int i = 0; i < vm.size(); i++) {
|
|
s.Printf(_("%d x %d - %dbpp @ %dHz"), vm[i].w, vm[i].h, vm[i].bpp, vm[i].refresh);
|
|
c->Append(s);
|
|
|
|
if (!modeno && gopts.fs_mode.w == vm[i].w && gopts.fs_mode.h == vm[i].h) {
|
|
if (gopts.fs_mode.bpp == vm[i].bpp && gopts.fs_mode.refresh == vm[i].refresh)
|
|
modeno = i + 1;
|
|
else if (vm[i].bpp == gopts.fs_mode.bpp && bm_bpp != gopts.fs_mode.bpp) {
|
|
bestmode = i + 1;
|
|
bm_bpp = vm[i].bpp;
|
|
} else if (bm_bpp != gopts.fs_mode.bpp && bm_bpp != 32 && vm[i].bpp == 32) {
|
|
bm_bpp = vm[i].bpp;
|
|
bestmode = i + 1;
|
|
} else if (bm_bpp != gopts.fs_mode.bpp && bm_bpp < 24 && vm[i].bpp == 24) {
|
|
bm_bpp = vm[i].bpp;
|
|
bestmode = i + 1;
|
|
} else if (bm_bpp != gopts.fs_mode.bpp && bm_bpp < 24 && bm_bpp != 16 && vm[i].bpp == 16) {
|
|
bm_bpp = vm[i].bpp;
|
|
bestmode = i + 1;
|
|
} else if (!bm_bpp) {
|
|
bm_bpp = vm[i].bpp;
|
|
bestmode = i + 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!modeno && bestmode)
|
|
modeno = bestmode;
|
|
|
|
c->SetSelection(modeno);
|
|
return true;
|
|
}
|
|
bool TransferFromWindow()
|
|
{
|
|
int bestmode = wxStaticCast(GetWindow(), wxChoice)->GetSelection();
|
|
|
|
if (!bestmode)
|
|
gopts.fs_mode.h = gopts.fs_mode.w = gopts.fs_mode.bpp = gopts.fs_mode.refresh = 0;
|
|
else
|
|
gopts.fs_mode = vm[bestmode - 1];
|
|
|
|
return true;
|
|
}
|
|
|
|
private:
|
|
wxArrayVideoModes vm;
|
|
};
|
|
|
|
// enable plugin-related iff filter choice is plugin
|
|
class PluginEnabler : public wxValidator {
|
|
public:
|
|
PluginEnabler()
|
|
: wxValidator()
|
|
{
|
|
}
|
|
PluginEnabler(const PluginEnabler& e)
|
|
: wxValidator()
|
|
{
|
|
}
|
|
wxObject* Clone() const { return new PluginEnabler(*this); }
|
|
bool TransferFromWindow() { return true; }
|
|
bool Validate(wxWindow* p) { return true; }
|
|
bool TransferToWindow()
|
|
{
|
|
GetWindow()->Enable(gopts.filter == FF_PLUGIN);
|
|
return true;
|
|
}
|
|
};
|
|
|
|
// The same, but as an event handler
|
|
static class PluginEnable_t : public wxEvtHandler {
|
|
public:
|
|
wxWindow *lab, *ch;
|
|
void ToggleChoice(wxCommandEvent& ev)
|
|
{
|
|
bool en = ev.GetSelection() == FF_PLUGIN;
|
|
lab->Enable(en);
|
|
ch->Enable(en);
|
|
}
|
|
} PluginEnableHandler;
|
|
|
|
// fill in plugin list
|
|
class PluginListFiller : public PluginEnabler {
|
|
public:
|
|
PluginListFiller(wxDialog* parent, wxControl* lab, wxChoice* ch)
|
|
: PluginEnabler()
|
|
, txt(lab)
|
|
, dlg(parent)
|
|
, plugins()
|
|
, filtch(ch)
|
|
{
|
|
}
|
|
PluginListFiller(const PluginListFiller& e)
|
|
: PluginEnabler()
|
|
, txt(e.txt)
|
|
, dlg(e.dlg)
|
|
, plugins(e.plugins)
|
|
, filtch(e.filtch)
|
|
{
|
|
}
|
|
wxObject* Clone() const { return new PluginListFiller(*this); }
|
|
bool Validate(wxWindow* p) { return true; }
|
|
bool TransferToWindow()
|
|
{
|
|
PluginEnabler::TransferToWindow();
|
|
wxChoice* ch = wxStaticCast(GetWindow(), wxChoice);
|
|
ch->Clear();
|
|
ch->Append(_("None"));
|
|
plugins.clear();
|
|
const wxString& plpath = wxStandardPaths::Get().GetPluginsDir();
|
|
wxDir::GetAllFiles(plpath, &plugins, wxT("*.rpi"));
|
|
|
|
for (int i = 0; i < plugins.size(); i++) {
|
|
wxDynamicLibrary dl(plugins[i], wxDL_VERBATIM | wxDL_NOW);
|
|
RENDPLUG_GetInfo GetInfo;
|
|
const RENDER_PLUGIN_INFO* rpi;
|
|
|
|
if (dl.IsLoaded() && (GetInfo = (RENDPLUG_GetInfo)dl.GetSymbol(wxT("RenderPluginGetInfo"))) &&
|
|
// note that in actual kega fusion plugins, rpi->Output is
|
|
// unused (as is rpi->Handle)
|
|
dl.GetSymbol(wxT("RenderPluginOutput")) && (rpi = GetInfo()) &&
|
|
// FIXME: maybe this should be >= RPI_VERISON
|
|
(rpi->Flags & 0xff) == RPI_VERSION &&
|
|
// RPI_565_SUPP is not supported
|
|
// although it would be possible
|
|
// and it would make Cairo more efficient
|
|
(rpi->Flags & (RPI_555_SUPP | RPI_888_SUPP))) {
|
|
wxFileName fn(plugins[i]);
|
|
wxString s = fn.GetName();
|
|
s += wxT(": ");
|
|
s += wxString(rpi->Name, wxConvUTF8, sizeof(rpi->Name));
|
|
fn.MakeRelativeTo(plpath);
|
|
plugins[i] = fn.GetFullName();
|
|
ch->Append(s);
|
|
|
|
if (plugins[i] == gopts.filter_plugin)
|
|
ch->SetSelection(i + 1);
|
|
} else
|
|
plugins.RemoveAt(i--);
|
|
}
|
|
|
|
if (ch->GetCount() == 1) {
|
|
// this is probably the only place the user can find out where
|
|
// to put the plugins... it depends on where program was
|
|
// installed, and of course OS
|
|
wxString msg;
|
|
msg.Printf(_("No usable rpi plugins found in %s"), plpath.mb_str());
|
|
systemScreenMessage(msg);
|
|
ch->Hide();
|
|
txt->Hide();
|
|
int cursel = filtch->GetSelection();
|
|
|
|
if (cursel == FF_PLUGIN)
|
|
cursel = 0;
|
|
|
|
if (filtch->GetCount() == FF_PLUGIN + 1) {
|
|
filtch->Delete(FF_PLUGIN);
|
|
// apparently wxgtk loses selection after this, even
|
|
// if selection was not FF_PLUGIN
|
|
filtch->SetSelection(cursel);
|
|
}
|
|
} else {
|
|
ch->Show();
|
|
txt->Show();
|
|
|
|
if (filtch->GetCount() < FF_PLUGIN + 1)
|
|
filtch->Append(_("Plugin"));
|
|
}
|
|
|
|
// FIXME: this isn't enough. It only resizes 2nd time around
|
|
dlg->Fit();
|
|
return true;
|
|
}
|
|
bool TransferFromWindow()
|
|
{
|
|
wxChoice* ch = wxStaticCast(GetWindow(), wxChoice);
|
|
|
|
if (ch->GetCount() == 1) {
|
|
gopts.filter_plugin = wxEmptyString;
|
|
|
|
// this happens if "Plugin" was selected and the entry was
|
|
// subsequently removed
|
|
if (ch->GetSelection() < 0)
|
|
ch->SetSelection(0);
|
|
|
|
if (gopts.filter < 0)
|
|
gopts.filter = 0;
|
|
} else {
|
|
int n = ch->GetSelection();
|
|
|
|
if (n > 0)
|
|
gopts.filter_plugin = plugins[n - 1];
|
|
else {
|
|
if (filtch->GetSelection() == FF_PLUGIN) {
|
|
wxMessageBox(_("Please select a plugin or a different filter"),
|
|
_("Plugin selection error"), wxOK | wxICON_ERROR);
|
|
return false;
|
|
}
|
|
|
|
gopts.filter_plugin = wxEmptyString;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private:
|
|
wxDialog* dlg;
|
|
wxControl* txt;
|
|
wxChoice* filtch;
|
|
wxArrayString plugins;
|
|
};
|
|
|
|
// this is the cmd table index for the accel tree ctrl
|
|
// one of the "benefits" of using TreeItemData is that we have to
|
|
// malloc them all, because treectrl destructor will free them all
|
|
// that means we can't use e.g. a single static table of len ncmds
|
|
class TreeInt : public wxTreeItemData {
|
|
public:
|
|
TreeInt(int i)
|
|
: wxTreeItemData()
|
|
{
|
|
val = i;
|
|
}
|
|
int val;
|
|
};
|
|
|
|
// Convert a tree selection ID to a name
|
|
// root
|
|
// parent
|
|
// item
|
|
static bool treeid_to_name(int id, wxString& name, wxTreeCtrl* tc,
|
|
const wxTreeItemId& parent, int lev = 0)
|
|
{
|
|
wxTreeItemIdValue cookie;
|
|
|
|
for (wxTreeItemId tid = tc->GetFirstChild(parent, cookie); tid.IsOk();
|
|
tid = tc->GetNextChild(parent, cookie)) {
|
|
const TreeInt* ti = static_cast<const TreeInt*>(tc->GetItemData(tid));
|
|
|
|
if (ti && ti->val == id) {
|
|
name = wxString(wxT(' '), 2 * lev) + tc->GetItemText(tid);
|
|
return true;
|
|
}
|
|
|
|
if (treeid_to_name(id, name, tc, tid, lev + 1)) {
|
|
name = wxString(wxT(' '), 2 * lev) + tc->GetItemText(tid) + wxT('\n') + name;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// for sorting accels by command ID
|
|
static bool cmdid_lt(const wxAcceleratorEntry& a, const wxAcceleratorEntry& b)
|
|
{
|
|
return a.GetCommand() < b.GetCommand();
|
|
}
|
|
|
|
// manage the accel editor dialog
|
|
static class AccelConfig_t : public wxEvtHandler {
|
|
public:
|
|
wxTreeCtrl* tc;
|
|
wxControlWithItems* lb;
|
|
wxAcceleratorEntry_v user_accels, accels;
|
|
wxWindow *asb, *remb;
|
|
wxKeyTextCtrl* key;
|
|
wxControl* curas;
|
|
|
|
// since this is not the actual dialog, derived from wxDialog, which is
|
|
// the normal way of doing things, do init on the show event instead of
|
|
// constructor
|
|
void Init(wxShowEvent& ev)
|
|
{
|
|
#if wxCHECK_VERSION(2, 9, 0)
|
|
#define GetShow IsShown
|
|
#endif
|
|
ev.Skip();
|
|
|
|
if (!ev.GetShow())
|
|
return;
|
|
|
|
lb->Clear();
|
|
tc->Unselect();
|
|
tc->ExpandAll();
|
|
user_accels = gopts.accels;
|
|
key->SetValue(wxT(""));
|
|
asb->Enable(false);
|
|
remb->Enable(false);
|
|
curas->SetLabel(wxT(""));
|
|
accels = wxGetApp().frame->get_accels(user_accels);
|
|
}
|
|
|
|
// on OK, save the accels in gopts
|
|
void Set(wxCommandEvent& ev)
|
|
{
|
|
// opts.cpp assumes that gopts.accels entries with same command ID
|
|
// are contiguous, so sort first
|
|
std::sort(gopts.accels.begin(), gopts.accels.end(), cmdid_lt);
|
|
gopts.accels = user_accels;
|
|
wxGetApp().frame->set_global_accels();
|
|
ev.Skip();
|
|
}
|
|
|
|
// After selecting item in command list, fill in key list
|
|
// and maybe enable asb
|
|
void CommandSel(wxTreeEvent& ev)
|
|
{
|
|
// wxTreeCtrl *tc = wxStaticCast(evt.GetEventObject(), wxTreeCtrl);
|
|
// can't use wxStaticCast; wxTreeItemData does not derive from wxObject
|
|
const TreeInt* id = static_cast<const TreeInt*>(tc->GetItemData(ev.GetItem()));
|
|
|
|
if (!id) {
|
|
ev.Veto();
|
|
return;
|
|
}
|
|
|
|
if (ev.GetEventType() == wxEVT_COMMAND_TREE_SEL_CHANGING) {
|
|
ev.Skip();
|
|
return;
|
|
}
|
|
|
|
lb->Clear();
|
|
remb->Enable(false);
|
|
asb->Enable(!key->GetValue().empty());
|
|
int cmd = id->val;
|
|
|
|
for (int i = 0; i < accels.size(); i++)
|
|
if (accels[i].GetCommand() == cmdtab[cmd].cmd_id)
|
|
lb->Append(wxKeyTextCtrl::ToString(accels[i].GetFlags(),
|
|
accels[i].GetKeyCode()));
|
|
}
|
|
|
|
// after selecting a key in key list, enable Remove button
|
|
void KeySel(wxCommandEvent& ev)
|
|
{
|
|
remb->Enable(lb->GetSelection() != wxNOT_FOUND);
|
|
}
|
|
|
|
// remove selected binding
|
|
void Remove(wxCommandEvent& ev)
|
|
{
|
|
int lsel = lb->GetSelection();
|
|
|
|
if (lsel == wxNOT_FOUND)
|
|
return;
|
|
|
|
wxString selstr = lb->GetString(lsel);
|
|
int selmod, selkey;
|
|
|
|
if (!wxKeyTextCtrl::FromString(selstr, selmod, selkey))
|
|
return; // this should never happen
|
|
|
|
remb->Enable(false);
|
|
|
|
// if this key is currently in the shortcut field, clear out curas
|
|
if (selstr == key->GetValue())
|
|
curas->SetLabel(wxT(""));
|
|
|
|
lb->Delete(lsel);
|
|
|
|
// first drop from user accels, if applicable
|
|
for (wxAcceleratorEntry_v::iterator i = user_accels.begin();
|
|
i < user_accels.end(); ++i)
|
|
if (i->GetFlags() == selmod && i->GetKeyCode() == selkey) {
|
|
user_accels.erase(i);
|
|
break;
|
|
}
|
|
|
|
// if it's a system accel, disable by assigning to NOOP
|
|
wxAcceleratorEntry_v& sys_accels = wxGetApp().frame->sys_accels;
|
|
|
|
for (int i = 0; i < sys_accels.size(); i++)
|
|
if (sys_accels[i].GetFlags() == selmod && sys_accels[i].GetKeyCode() == selkey) {
|
|
wxAcceleratorEntry ne(selmod, selkey, XRCID("NOOP"));
|
|
user_accels.push_back(ne);
|
|
}
|
|
|
|
// finally, remove from accels instead of recomputing
|
|
for (wxAcceleratorEntry_v::iterator i = accels.begin();
|
|
i < accels.end(); ++i)
|
|
if (i->GetFlags() == selmod && i->GetKeyCode() == selkey) {
|
|
accels.erase(i);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// wipe out all user bindings
|
|
void ResetAll(wxCommandEvent& ev)
|
|
{
|
|
if (user_accels.empty() || wxMessageBox(_("This will clear all user-defined accelerators. Are you sure?"), _("Confirm"), wxYES_NO) != wxYES)
|
|
return;
|
|
|
|
user_accels.clear();
|
|
accels = wxGetApp().frame->sys_accels;
|
|
tc->Unselect();
|
|
lb->Clear();
|
|
// rather than recomputing curas, just clear it
|
|
key->SetValue(wxT(""));
|
|
curas->SetLabel(wxT(""));
|
|
}
|
|
|
|
// remove old key binding, add new key binding, and update GUI
|
|
void Assign(wxCommandEvent& ev)
|
|
{
|
|
wxTreeItemId csel = tc->GetSelection();
|
|
wxString accel = key->GetValue();
|
|
|
|
if (!csel.IsOk() || accel.empty())
|
|
return;
|
|
|
|
int acmod, ackey;
|
|
|
|
if (!wxKeyTextCtrl::FromString(accel, acmod, ackey))
|
|
return; // this should never happen
|
|
|
|
for (int i = 0; i < lb->GetCount(); i++)
|
|
if (lb->GetString(i) == accel)
|
|
return; // ignore attempts to add twice
|
|
|
|
lb->Append(accel);
|
|
|
|
// first drop from user accels, if applicable
|
|
for (wxAcceleratorEntry_v::iterator i = user_accels.begin();
|
|
i < user_accels.end(); ++i)
|
|
if (i->GetFlags() == acmod && i->GetKeyCode() == ackey) {
|
|
user_accels.erase(i);
|
|
break;
|
|
}
|
|
|
|
// then assign to this command
|
|
const TreeInt* id = static_cast<const TreeInt*>(tc->GetItemData(csel));
|
|
wxAcceleratorEntry ne(acmod, ackey, cmdtab[id->val].cmd_id);
|
|
user_accels.push_back(ne);
|
|
// now assigned to this cmd...
|
|
wxString lab;
|
|
treeid_to_name(id->val, lab, tc, tc->GetRootItem());
|
|
curas->SetLabel(lab);
|
|
// finally, instead of recomputing accels, just append new accel
|
|
accels.push_back(ne);
|
|
}
|
|
|
|
// update curas and maybe enable asb
|
|
void CheckKey(wxCommandEvent& ev)
|
|
{
|
|
wxString nkey = key->GetValue();
|
|
|
|
if (nkey.empty()) {
|
|
curas->SetLabel(wxT(""));
|
|
asb->Enable(false);
|
|
return;
|
|
}
|
|
|
|
int acmod, ackey;
|
|
|
|
if (!wxKeyTextCtrl::FromString(nkey, acmod, ackey)) {
|
|
// this should never happen
|
|
key->SetValue(wxT(""));
|
|
asb->Enable(false);
|
|
return;
|
|
}
|
|
|
|
asb->Enable(tc->GetSelection().IsOk());
|
|
int cmd = -1;
|
|
|
|
for (int i = 0; i < accels.size(); i++)
|
|
if (accels[i].GetFlags() == acmod && accels[i].GetKeyCode() == ackey) {
|
|
int cmdid = accels[i].GetCommand();
|
|
|
|
for (cmd = 0; cmd < ncmds; cmd++)
|
|
if (cmdid == cmdtab[cmd].cmd_id)
|
|
break;
|
|
|
|
break;
|
|
}
|
|
|
|
if (cmd < 0 || cmdtab[cmd].cmd_id == XRCID("NOOP")) {
|
|
curas->SetLabel(wxT(""));
|
|
return;
|
|
}
|
|
|
|
wxString lab;
|
|
treeid_to_name(cmd, lab, tc, tc->GetRootItem());
|
|
curas->SetLabel(lab);
|
|
}
|
|
} accel_config_handler;
|
|
|
|
// build initial accel tree control from menu
|
|
void MainFrame::add_menu_accels(wxTreeCtrl* tc, wxTreeItemId& parent, wxMenu* menu)
|
|
{
|
|
wxMenuItemList mil = menu->GetMenuItems();
|
|
|
|
for (wxMenuItemList::iterator mi = mil.begin(); mi != mil.end(); ++mi) {
|
|
if ((*mi)->IsSeparator()) {
|
|
tc->AppendItem(parent, wxT("-----"));
|
|
} else if ((*mi)->IsSubMenu()) {
|
|
wxTreeItemId id = tc->AppendItem(parent, (*mi)->GetItemLabelText());
|
|
add_menu_accels(tc, id, (*mi)->GetSubMenu());
|
|
|
|
if ((*mi)->GetSubMenu() == recent) {
|
|
for (int i = wxID_FILE1; i <= wxID_FILE10; i++) {
|
|
int cmdid;
|
|
|
|
for (cmdid = 0; cmdid < ncmds; cmdid++)
|
|
if (cmdtab[cmdid].cmd_id == i)
|
|
break;
|
|
|
|
TreeInt* val = new TreeInt(cmdid);
|
|
tc->AppendItem(id, cmdtab[cmdid].name, -1, -1, val);
|
|
}
|
|
}
|
|
} else {
|
|
int mid = (*mi)->GetId();
|
|
|
|
if (mid >= wxID_FILE1 && mid <= wxID_FILE10)
|
|
continue;
|
|
|
|
int cmdid;
|
|
|
|
for (cmdid = 0; cmdid < ncmds; cmdid++)
|
|
if (cmdtab[cmdid].cmd_id == mid)
|
|
break;
|
|
|
|
if (cmdid == ncmds)
|
|
continue; // bad menu item; should inform user really
|
|
|
|
TreeInt* val = new TreeInt(cmdid);
|
|
// ugh. There has to be a better way...
|
|
// perhaps make XRCID ranges a requirement for load/save st?
|
|
// but then if the user overides main menu, that req. is broken..
|
|
wxString txt = (*mi)->GetItemLabelText();
|
|
|
|
for (int i = 0; i < 10; i++)
|
|
if (*mi == loadst_mi[i] || *mi == savest_mi[i]) {
|
|
txt = cmdtab[i].name;
|
|
break;
|
|
}
|
|
|
|
tc->AppendItem(parent, txt, -1, -1, val);
|
|
}
|
|
}
|
|
}
|
|
|
|
// manage throttle spinctrl/canned setting choice interaction
|
|
static class ThrottleCtrl_t : public wxEvtHandler {
|
|
public:
|
|
wxSpinCtrl* thr;
|
|
wxChoice* thrsel;
|
|
|
|
// set thrsel from thr
|
|
void SetThrottleSel(wxSpinEvent& evt)
|
|
{
|
|
DoSetThrottleSel(thr->GetValue());
|
|
}
|
|
|
|
void DoSetThrottleSel(int val)
|
|
{
|
|
switch (val) {
|
|
case 0:
|
|
thrsel->SetSelection(1);
|
|
break;
|
|
|
|
case 25:
|
|
thrsel->SetSelection(2);
|
|
break;
|
|
|
|
case 50:
|
|
thrsel->SetSelection(3);
|
|
break;
|
|
|
|
case 100:
|
|
thrsel->SetSelection(4);
|
|
break;
|
|
|
|
case 150:
|
|
thrsel->SetSelection(5);
|
|
break;
|
|
|
|
case 200:
|
|
thrsel->SetSelection(6);
|
|
break;
|
|
|
|
default:
|
|
thrsel->SetSelection(0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// set thr from thrsel
|
|
void SetThrottle(wxCommandEvent& evt)
|
|
{
|
|
switch (thrsel->GetSelection()) {
|
|
case 0: // blank; leave it alone
|
|
break;
|
|
|
|
case 1:
|
|
thr->SetValue(0);
|
|
break;
|
|
|
|
case 2:
|
|
thr->SetValue(25);
|
|
break;
|
|
|
|
case 3:
|
|
thr->SetValue(50);
|
|
break;
|
|
|
|
case 4:
|
|
thr->SetValue(100);
|
|
break;
|
|
|
|
case 5:
|
|
thr->SetValue(150);
|
|
break;
|
|
|
|
case 6:
|
|
thr->SetValue(200);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// since this is not the actual dialog, derived from wxDialog, which is
|
|
// the normal way of doing things, do init on the show event instead of
|
|
// constructor
|
|
// could've also used a validator, I guess...
|
|
void Init(wxShowEvent& ev)
|
|
{
|
|
ev.Skip();
|
|
DoSetThrottleSel(throttle);
|
|
}
|
|
} throttle_ctrl;
|
|
|
|
/////////////////////////////
|
|
//Check if a pointer from the XRC file is valid. If it's not, throw an error telling the user.
|
|
template <typename T>
|
|
void CheckThrowXRCError(T pointer, const wxString& name)
|
|
{
|
|
if (pointer == NULL) {
|
|
std::string errormessage = "Unable to load a \"";
|
|
errormessage += typeid(pointer).name();
|
|
errormessage += "\" from the builtin xrc file: ";
|
|
errormessage += name.mb_str();
|
|
throw std::runtime_error(errormessage);
|
|
}
|
|
}
|
|
|
|
template <typename T>
|
|
void CheckThrowXRCError(T pointer, const char* name)
|
|
{
|
|
if (pointer == NULL) {
|
|
std::string errormessage = "Unable to load a \"";
|
|
errormessage += typeid(pointer).name();
|
|
errormessage += "\" from the builtin xrc file: ";
|
|
errormessage += name;
|
|
throw std::runtime_error(errormessage);
|
|
}
|
|
}
|
|
|
|
wxDialog* MainFrame::LoadXRCDialog(const char* name)
|
|
{
|
|
wxString dname = wxString::FromUTF8(name);
|
|
wxDialog* dialog = wxXmlResource::Get()->LoadDialog(this, dname);
|
|
CheckThrowXRCError(dialog, name);
|
|
/* wx-2.9.1 doesn't set parent for propertysheetdialogs for some reason */
|
|
/* this will generate a gtk warning but it is necessary for later */
|
|
/* retrieval using FindWindow() */
|
|
#if (wxMAJOR_VERSION < 3)
|
|
|
|
if (!dialog->GetParent())
|
|
dialog->Reparent(this);
|
|
|
|
#endif
|
|
mark_recursive(dialog);
|
|
return dialog;
|
|
}
|
|
|
|
wxDialog* MainFrame::LoadXRCropertySheetDialog(const char* name)
|
|
{
|
|
wxString dname = wxString::FromUTF8(name);
|
|
//Seems like the only way to do this
|
|
wxObject* anObject = wxXmlResource::Get()->LoadObject(this, dname, wxEmptyString);
|
|
wxDialog* dialog = dynamic_cast<wxDialog*>(anObject);
|
|
CheckThrowXRCError(dialog, name);
|
|
/* wx-2.9.1 doesn't set parent for propertysheetdialogs for some reason */
|
|
/* this will generate a gtk warning but it is necessary for later */
|
|
/* retrieval using FindWindow() */
|
|
#if (wxMAJOR_VERSION < 3)
|
|
|
|
if (!dialog->GetParent())
|
|
dialog->Reparent(this);
|
|
|
|
#endif
|
|
mark_recursive(dialog);
|
|
return dialog;
|
|
}
|
|
|
|
//This just adds some error checking to the wx XRCCTRL macro
|
|
template <typename T>
|
|
T* SafeXRCCTRL(wxWindow* parent, const char* name)
|
|
{
|
|
wxString dname = wxString::FromUTF8(name);
|
|
//This is needed to work around a bug in XRCCTRL
|
|
wxString Ldname = dname;
|
|
T* output = XRCCTRL_D(*parent, dname, T);
|
|
CheckThrowXRCError(output, name);
|
|
return output;
|
|
}
|
|
|
|
template <typename T>
|
|
T* SafeXRCCTRL(wxWindow* parent, const wxString& name)
|
|
{
|
|
T* output = XRCCTRL_D(*parent, name, T);
|
|
CheckThrowXRCError(output, name);
|
|
return output;
|
|
}
|
|
|
|
///Get an object, and set the appropriate validator
|
|
//T is the object type, and V is the validator type
|
|
template <typename T, typename V>
|
|
T* GetValidatedChild(wxWindow* parent, const char* name, V validator)
|
|
{
|
|
T* child = SafeXRCCTRL<T>(parent, name);
|
|
child->SetValidator(validator);
|
|
return child;
|
|
}
|
|
|
|
wxAcceleratorEntry_v MainFrame::get_accels(wxAcceleratorEntry_v user_accels)
|
|
{
|
|
// set global accelerators
|
|
// first system
|
|
wxAcceleratorEntry_v accels = sys_accels;
|
|
|
|
// then user overrides
|
|
// silently keep only last defined binding
|
|
// same horribly inefficent O(n*m) search for duplicates as above..
|
|
for (int i = 0; i < user_accels.size(); i++) {
|
|
const wxAcceleratorEntry& ae = user_accels[i];
|
|
|
|
for (wxAcceleratorEntry_v::iterator e = accels.begin(); e < accels.end(); ++e)
|
|
if (ae.GetFlags() == e->GetFlags() && ae.GetKeyCode() == e->GetKeyCode()) {
|
|
accels.erase(e);
|
|
break;
|
|
}
|
|
|
|
accels.push_back(ae);
|
|
}
|
|
|
|
return accels;
|
|
}
|
|
|
|
void MainFrame::set_global_accels()
|
|
{
|
|
wxAcceleratorEntry_v accels = get_accels(gopts.accels);
|
|
// this is needed on Wine/win32 to support accels for close & quit
|
|
wxGetApp().accels = accels;
|
|
|
|
// Update menus; this probably takes the longest
|
|
// as a side effect, any system-defined accels that weren't already in
|
|
// the menus will be added now
|
|
|
|
// first, zero out menu item on all accels
|
|
for (int i = 0; i < accels.size(); i++)
|
|
accels[i].Set(accels[i].GetFlags(), accels[i].GetKeyCode(),
|
|
accels[i].GetCommand());
|
|
|
|
// yet another O(n*m) loop. I really ought to sort the accel arrays
|
|
for (int i = 0; i < ncmds; i++) {
|
|
wxMenuItem* mi = cmdtab[i].mi;
|
|
|
|
if (!mi)
|
|
continue;
|
|
|
|
// only *last* accelerator is made visible in menu
|
|
// and is flagged as such by setting menu item in accel
|
|
// the last is chosen so menu overrides non-menu and user overrides
|
|
// system
|
|
int cmd = cmdtab[i].cmd_id;
|
|
int last_accel = -1;
|
|
|
|
for (int j = 0; j < accels.size(); j++)
|
|
if (cmd == accels[j].GetCommand())
|
|
last_accel = j;
|
|
|
|
if (last_accel >= 0) {
|
|
DoSetAccel(mi, &accels[last_accel]);
|
|
accels[last_accel].Set(accels[last_accel].GetFlags(),
|
|
accels[last_accel].GetKeyCode(),
|
|
accels[last_accel].GetCommand(), mi);
|
|
} else
|
|
// clear out user-cleared menu items
|
|
DoSetAccel(mi, NULL);
|
|
}
|
|
|
|
// Finally, install a global accelerator table for any non-menu accels
|
|
int len = 0;
|
|
|
|
for (int i = 0; i < accels.size(); i++)
|
|
if (!accels[i].GetMenuItem())
|
|
len++;
|
|
|
|
if (len) {
|
|
wxAcceleratorEntry tab[1000];
|
|
|
|
for (int i = 0, j = 0; i < accels.size(); i++)
|
|
if (!accels[i].GetMenuItem())
|
|
tab[j++] = accels[i];
|
|
|
|
wxAcceleratorTable atab(len, tab);
|
|
// set the table on the panel, where focus usually is
|
|
// otherwise accelerators are lost sometimes
|
|
panel->SetAcceleratorTable(atab);
|
|
} else
|
|
panel->SetAcceleratorTable(wxNullAcceleratorTable);
|
|
|
|
// save recent accels
|
|
for (int i = 0; i < 10; i++)
|
|
recent_accel[i] = wxAcceleratorEntry();
|
|
|
|
for (int i = 0; i < accels.size(); i++)
|
|
if (accels[i].GetCommand() >= wxID_FILE1 && accels[i].GetCommand() <= wxID_FILE10)
|
|
recent_accel[accels[i].GetCommand() - wxID_FILE1] = accels[i];
|
|
|
|
SetRecentAccels();
|
|
}
|
|
|
|
void MainFrame::MenuOptionBool(const char* menuName, bool& field)
|
|
{
|
|
int id = wxXmlResource::GetXRCID(wxString(menuName, wxConvUTF8));
|
|
|
|
for (int i = 0; i < checkable_mi.size(); i++) {
|
|
if (checkable_mi[i].cmd != id)
|
|
continue;
|
|
|
|
checkable_mi[i].boolopt = &field;
|
|
checkable_mi[i].mi->Check(field);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void MainFrame::MenuOptionIntMask(const char* menuName, int& field, int mask)
|
|
{
|
|
int id = wxXmlResource::GetXRCID(wxString(menuName, wxConvUTF8));
|
|
int value = mask;
|
|
|
|
for (int i = 0; i < checkable_mi.size(); i++) {
|
|
if (checkable_mi[i].cmd != id)
|
|
continue;
|
|
|
|
checkable_mi[i].intopt = &field;
|
|
checkable_mi[i].mask = mask;
|
|
checkable_mi[i].val = value;
|
|
checkable_mi[i].mi->Check((field & mask) == value);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void MainFrame::MenuOptionIntRadioValue(const char* menuName, int& field, int value)
|
|
{
|
|
int id = wxXmlResource::GetXRCID(wxString(menuName, wxConvUTF8));
|
|
|
|
for (int i = 0; i < checkable_mi.size(); i++) {
|
|
if (checkable_mi[i].cmd != id)
|
|
continue;
|
|
|
|
checkable_mi[i].intopt = &field;
|
|
checkable_mi[i].val = field;
|
|
checkable_mi[i].mi->Check(field == value);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// The Windows icon loading code is from:
|
|
//
|
|
// https://stackoverflow.com/questions/17949693/windows-volume-mixer-icon-size-is-too-large/46310786#46310786
|
|
//
|
|
// This works around a Windows bug where the icon is too large in the
|
|
// app-specific volume controls.
|
|
|
|
#ifdef __WXMSW__
|
|
#include <windows.h>
|
|
#include <versionhelpers.h>
|
|
#include <commctrl.h>
|
|
#include <wx/msw/private.h>
|
|
typedef int (WINAPI *func_LoadIconWithScaleDown)(HINSTANCE, LPCWSTR, int, int, HICON*);
|
|
#endif
|
|
|
|
void MainFrame::BindAppIcon() {
|
|
#ifdef __WXMSW__
|
|
if (IsWindowsVistaOrGreater()) {
|
|
wxDynamicLibrary comctl32("comctl32", wxDL_DEFAULT | wxDL_QUIET);
|
|
func_LoadIconWithScaleDown load_icon_scaled = reinterpret_cast<func_LoadIconWithScaleDown>(comctl32.GetSymbol("LoadIconWithScaleDown"));
|
|
int icon_set_count = 0;
|
|
|
|
HICON hIconLg;
|
|
if (load_icon_scaled && SUCCEEDED(load_icon_scaled(wxGetInstance(), _T("AAAAA_MAINICON"), ::GetSystemMetrics(SM_CXICON), ::GetSystemMetrics(SM_CYICON), &hIconLg))) {
|
|
::SendMessage(GetHandle(), WM_SETICON, ICON_BIG, reinterpret_cast<LPARAM>(hIconLg));
|
|
++icon_set_count;
|
|
}
|
|
HICON hIconSm;
|
|
if (load_icon_scaled && SUCCEEDED(load_icon_scaled(wxGetInstance(), _T("AAAAA_MAINICON"), ::GetSystemMetrics(SM_CXSMICON), ::GetSystemMetrics(SM_CYSMICON), &hIconSm))) {
|
|
::SendMessage(GetHandle(), WM_SETICON, ICON_SMALL, reinterpret_cast<LPARAM>(hIconSm));
|
|
++icon_set_count;
|
|
}
|
|
|
|
if (icon_set_count == 2) return;
|
|
}
|
|
// otherwise fall back to Wx method of setting icon
|
|
#endif
|
|
wxIcon icon = wxXmlResource::Get()->LoadIcon(wxT("MainIcon"));
|
|
|
|
if (!icon.IsOk()) {
|
|
wxLogInfo(_("Main icon not found"));
|
|
icon = wxICON(wxvbam);
|
|
}
|
|
|
|
SetIcon(icon);
|
|
}
|
|
|
|
// If there is a menubar, store all special menuitems
|
|
#define XRCITEM_I(id) menubar->FindItem(id, NULL)
|
|
#define XRCITEM_D(s) XRCITEM_I(XRCID_D(s))
|
|
#define XRCITEM(s) XRCITEM_D(wxT(s))
|
|
|
|
bool MainFrame::BindControls()
|
|
{
|
|
// Make sure display panel present and correct type
|
|
panel = XRCCTRL(*this, "DisplayArea", GameArea);
|
|
|
|
if (!panel) {
|
|
wxLogError(_("Main display panel not found"));
|
|
return false;
|
|
}
|
|
|
|
panel->SetMainFrame(this);
|
|
|
|
panel->AdjustSize(false);
|
|
// only the panel does idle events (the emulator loop)
|
|
// however, do not enable until end of init, since errors will start
|
|
// the idle loop on wxGTK
|
|
wxIdleEvent::SetMode(wxIDLE_PROCESS_SPECIFIED);
|
|
|
|
BindAppIcon();
|
|
|
|
// NOOP if no status area
|
|
SetStatusText(wxT(""));
|
|
|
|
// Prepare system accel table
|
|
for (int i = 0; i < num_def_accels; i++)
|
|
sys_accels.push_back(default_accels[i]);
|
|
|
|
wxMenuBar* menubar = GetMenuBar();
|
|
ctx_menu = NULL;
|
|
|
|
if (menubar) {
|
|
#if 0 // doesn't work in 2.9 at all (causes main menu to malfunction)
|
|
// to fix, recursively copy entire menu insted of just copying
|
|
// menubar. This means that every saved menu item must also be
|
|
// saved twice... A lot of work for a mostly worthless feature.
|
|
// If you want the menu, just exit full-screen mode.
|
|
// Either that, or add an option to retain the regular
|
|
// menubar in full-screen mode
|
|
// create a context menu for fullscreen mode
|
|
// FIXME: on gtk port, this gives Gtk-WARNING **:
|
|
// gtk_menu_attach_to_widget(): menu already attached to GtkMenuItem
|
|
// but it works anyway
|
|
// Note: menu default accelerators (e.g. alt-f for file menu) don't
|
|
// work with context menu (and can never work, since there is no
|
|
// way to pop up a submenu)
|
|
// It would probably be better, in the end, to use a collapsed menu
|
|
// bar (either Amiga-style press RMB to make appear, or Windows
|
|
// collapsed toolbar-style move mouse to within a pixel of top to
|
|
// make appear). Not supported in wx without a lot of work, though.
|
|
// Maybe this feature should just be dropped; the user would simply
|
|
// have to exit fullscreen mode to use the menu.
|
|
ctx_menu = new wxMenu();
|
|
|
|
for (int i = 0; i < menubar->GetMenuCount(); i++)
|
|
ctx_menu->AppendSubMenu(menubar->GetMenu(i), menubar->GetMenuLabel(i));
|
|
|
|
#endif
|
|
|
|
// save all menu items in the command table
|
|
for (int i = 0; i < ncmds; i++) {
|
|
wxMenuItem* mi = cmdtab[i].mi = XRCITEM_I(cmdtab[i].cmd_id);
|
|
// remove unsupported commands first
|
|
#ifdef NO_FFMPEG
|
|
|
|
if (cmdtab[i].mask_flags & (CMDEN_SREC | CMDEN_NSREC | CMDEN_VREC | CMDEN_NVREC)) {
|
|
if (mi)
|
|
mi->GetMenu()->Remove(mi);
|
|
|
|
cmdtab[i].cmd_id = XRCID("NOOP");
|
|
cmdtab[i].mi = NULL;
|
|
continue;
|
|
}
|
|
|
|
#endif
|
|
#ifndef GBA_LOGGING
|
|
|
|
if (cmdtab[i].cmd_id == XRCID("Logging")) {
|
|
if (mi)
|
|
mi->GetMenu()->Remove(mi);
|
|
|
|
cmdtab[i].cmd_id = XRCID("NOOP");
|
|
cmdtab[i].mi = NULL;
|
|
continue;
|
|
}
|
|
|
|
#endif
|
|
#ifdef NO_LINK
|
|
|
|
if (cmdtab[i].cmd_id == XRCID("LanLink") || cmdtab[i].cmd_id == XRCID("LinkType0Nothing") || cmdtab[i].cmd_id == XRCID("LinkType1Cable") || cmdtab[i].cmd_id == XRCID("LinkType2Wireless") || cmdtab[i].cmd_id == XRCID("LinkType3GameCube") || cmdtab[i].cmd_id == XRCID("LinkType4Gameboy") || cmdtab[i].cmd_id == XRCID("LinkAuto") || cmdtab[i].cmd_id == XRCID("SpeedOn") || cmdtab[i].cmd_id == XRCID("LinkProto") || cmdtab[i].cmd_id == XRCID("LinkConfigure")) {
|
|
if (mi)
|
|
mi->GetMenu()->Remove(mi);
|
|
|
|
cmdtab[i].cmd_id = XRCID("NOOP");
|
|
cmdtab[i].mi = NULL;
|
|
continue;
|
|
}
|
|
|
|
#endif
|
|
|
|
if (mi) {
|
|
// wxgtk provides no way to retrieve stock label/accel
|
|
// and does not override wxGetStockLabel()
|
|
// as of 2.8.12/2.9.1
|
|
// so override with wx's stock label <sigh>
|
|
// at least you still get gtk's stock icon
|
|
if (mi->GetItemLabel().empty())
|
|
mi->SetItemLabel(wxGetStockLabel(mi->GetId(),
|
|
wxSTOCK_WITH_MNEMONIC | wxSTOCK_WITH_ACCELERATOR));
|
|
|
|
// add accelerator to global accel table
|
|
wxAcceleratorEntry* a = mi->GetAccel();
|
|
|
|
if (a) {
|
|
a->Set(a->GetFlags(), a->GetKeyCode(), cmdtab[i].cmd_id, mi);
|
|
|
|
// only add it if not already there
|
|
for (wxAcceleratorEntry_v::iterator e = sys_accels.begin();
|
|
e < sys_accels.end(); ++e)
|
|
if (a->GetFlags() == e->GetFlags() && a->GetKeyCode() == e->GetKeyCode()) {
|
|
if (e->GetMenuItem()) {
|
|
wxLogInfo(_("Duplicate menu accelerator: %s for %s and %s; keeping first"),
|
|
wxKeyTextCtrl::ToString(a->GetFlags(), a->GetKeyCode()).mb_str(),
|
|
e->GetMenuItem()->GetItemLabelText().mb_str(),
|
|
mi->GetItemLabelText().mb_str());
|
|
delete a;
|
|
a = 0;
|
|
} else {
|
|
if (e->GetCommand() != a->GetCommand()) {
|
|
int cmd;
|
|
|
|
for (cmd = 0; cmd < ncmds; cmd++)
|
|
if (cmdtab[cmd].cmd_id == e->GetCommand())
|
|
break;
|
|
|
|
wxLogInfo(_("Menu accelerator %s for %s overrides default for %s ; keeping menu"),
|
|
wxKeyTextCtrl::ToString(a->GetFlags(), a->GetKeyCode()).mb_str(),
|
|
mi->GetItemLabelText().mb_str(),
|
|
cmdtab[cmd].cmd);
|
|
}
|
|
|
|
sys_accels.erase(e);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
if (a)
|
|
sys_accels.push_back(*a);
|
|
else
|
|
// strip from label so user isn't confused
|
|
DoSetAccel(mi, NULL);
|
|
}
|
|
|
|
// store checkable items
|
|
if (mi->IsCheckable()) {
|
|
checkable_mi_t cmi = { cmdtab[i].cmd_id, mi };
|
|
checkable_mi.push_back(cmi);
|
|
|
|
for (int j = 0; j < num_opts; j++) {
|
|
wxString menuName = wxString(opts[j].cmd, wxConvUTF8);
|
|
|
|
if (menuName == cmdtab[i].cmd) {
|
|
if (opts[j].intopt)
|
|
MenuOptionIntMask(opts[j].cmd, *opts[j].intopt, (1 << 0));
|
|
else if (opts[j].boolopt)
|
|
MenuOptionBool(opts[j].cmd, *opts[j].boolopt);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// if a recent menu is present, save its location
|
|
wxMenuItem* recentmi = XRCITEM("RecentMenu");
|
|
|
|
if (recentmi && recentmi->IsSubMenu()) {
|
|
recent = recentmi->GetSubMenu();
|
|
gopts.recent->UseMenu(recent);
|
|
gopts.recent->AddFilesToMenu();
|
|
} else
|
|
recent = NULL;
|
|
|
|
// if save/load state menu items present, save their locations
|
|
for (int i = 0; i < 10; i++) {
|
|
wxString n;
|
|
n.Printf(wxT("LoadGame%02d"), i + 1);
|
|
loadst_mi[i] = XRCITEM_D(n);
|
|
n.Printf(wxT("SaveGame%02d"), i + 1);
|
|
savest_mi[i] = XRCITEM_D(n);
|
|
}
|
|
} else {
|
|
recent = NULL;
|
|
|
|
for (int i = 0; i < 10; i++)
|
|
loadst_mi[i] = savest_mi[i] = NULL;
|
|
}
|
|
|
|
// just setting to UNLOAD_CMDEN_KEEP is invalid
|
|
// so just set individual flags here
|
|
cmd_enable = CMDEN_NGDB_ANY | CMDEN_NREC_ANY;
|
|
update_state_ts(true);
|
|
|
|
// set pointers for checkable menu items
|
|
// and set initial checked status
|
|
if (checkable_mi.size()) {
|
|
MenuOptionBool("RecentFreeze", gopts.recent_freeze);
|
|
MenuOptionBool("Pause", paused);
|
|
MenuOptionIntMask("SoundChannel1", gopts.sound_en, (1 << 0));
|
|
MenuOptionIntMask("SoundChannel2", gopts.sound_en, (1 << 1));
|
|
MenuOptionIntMask("SoundChannel3", gopts.sound_en, (1 << 2));
|
|
MenuOptionIntMask("SoundChannel4", gopts.sound_en, (1 << 3));
|
|
MenuOptionIntMask("DirectSoundA", gopts.sound_en, (1 << 8));
|
|
MenuOptionIntMask("DirectSoundB", gopts.sound_en, (1 << 9));
|
|
MenuOptionIntMask("VideoLayersBG0", layerSettings, (1 << 8));
|
|
MenuOptionIntMask("VideoLayersBG1", layerSettings, (1 << 9));
|
|
MenuOptionIntMask("VideoLayersBG2", layerSettings, (1 << 10));
|
|
MenuOptionIntMask("VideoLayersBG3", layerSettings, (1 << 11));
|
|
MenuOptionIntMask("VideoLayersOBJ", layerSettings, (1 << 12));
|
|
MenuOptionIntMask("VideoLayersWIN0", layerSettings, (1 << 13));
|
|
MenuOptionIntMask("VideoLayersWIN1", layerSettings, (1 << 14));
|
|
MenuOptionIntMask("VideoLayersOBJWIN", layerSettings, (1 << 15));
|
|
MenuOptionBool("CheatsAutoSaveLoad", gopts.autoload_cheats);
|
|
MenuOptionIntMask("CheatsEnable", cheatsEnabled, 1);
|
|
MenuOptionIntMask("KeepSaves", skipSaveGameBattery, 1);
|
|
MenuOptionIntMask("KeepCheats", skipSaveGameCheats, 1);
|
|
MenuOptionBool("LoadGameAutoLoad", gopts.autoload_state);
|
|
MenuOptionIntMask("JoypadAutofireA", autofire, KEYM_A);
|
|
MenuOptionIntMask("JoypadAutofireB", autofire, KEYM_B);
|
|
MenuOptionIntMask("JoypadAutofireL", autofire, KEYM_LEFT);
|
|
MenuOptionIntMask("JoypadAutofireR", autofire, KEYM_RIGHT);
|
|
MenuOptionBool("EmulatorSpeedupToggle", turbo);
|
|
MenuOptionIntRadioValue("LinkType0Nothing", gopts.gba_link_type, 0);
|
|
MenuOptionIntRadioValue("LinkType1Cable", gopts.gba_link_type, 1);
|
|
MenuOptionIntRadioValue("LinkType2Wireless", gopts.gba_link_type, 2);
|
|
MenuOptionIntRadioValue("LinkType3GameCube", gopts.gba_link_type, 3);
|
|
MenuOptionIntRadioValue("LinkType4Gameboy", gopts.gba_link_type, 4);
|
|
}
|
|
|
|
for (int i = 0; i < checkable_mi.size(); i++)
|
|
if (!checkable_mi[i].boolopt && !checkable_mi[i].intopt) {
|
|
wxLogError(_("Invalid menu item %s; removing"),
|
|
checkable_mi[i].mi->GetItemLabelText().mb_str());
|
|
checkable_mi[i].mi->GetMenu()->Remove(checkable_mi[i].mi);
|
|
checkable_mi[i].mi = NULL;
|
|
}
|
|
|
|
set_global_accels();
|
|
|
|
// preload and verify all resource dialogs
|
|
// this will take init time and memory, but catches errors in xrc sooner
|
|
// note that the only verification done is to ensure no crashes. It's the
|
|
// user's responsibility to ensure that the GUI works as intended after
|
|
// modifications
|
|
try {
|
|
wxDialog* d = NULL;
|
|
//// displayed during run
|
|
d = LoadXRCDialog("GBPrinter");
|
|
// just verify preview window & mag sel present
|
|
{
|
|
wxPanel* prev;
|
|
prev = SafeXRCCTRL<wxPanel>(d, "Preview");
|
|
|
|
if (!wxDynamicCast(prev->GetParent(), wxScrolledWindow))
|
|
throw std::runtime_error("Unable to load a dialog control from the builtin xrc file: Preview");
|
|
|
|
SafeXRCCTRL<wxControlWithItems>(d, "Magnification");
|
|
d->Fit();
|
|
}
|
|
//// File menu
|
|
d = LoadXRCDialog("GBAROMInfo");
|
|
// just verify fields present
|
|
wxControl* lab;
|
|
#define getlab(n) lab = SafeXRCCTRL<wxControl>(d, n)
|
|
getlab("Title");
|
|
getlab("GameCode");
|
|
getlab("MakerCode");
|
|
getlab("MakerName");
|
|
getlab("UnitCode");
|
|
getlab("DeviceType");
|
|
getlab("Version");
|
|
getlab("CRC");
|
|
d->Fit();
|
|
d = LoadXRCDialog("GBROMInfo");
|
|
// just verify fields present
|
|
getlab("Title");
|
|
getlab("MakerCode");
|
|
getlab("MakerName");
|
|
getlab("UnitCode");
|
|
getlab("DeviceType");
|
|
getlab("Version");
|
|
getlab("CRC");
|
|
getlab("Color");
|
|
getlab("ROMSize");
|
|
getlab("RAMSize");
|
|
getlab("DestCode");
|
|
getlab("LicCode");
|
|
getlab("Checksum");
|
|
d->Fit();
|
|
d = LoadXRCDialog("CodeSelect");
|
|
// just verify list present
|
|
SafeXRCCTRL<wxControlWithItems>(d, "CodeList");
|
|
d->Fit();
|
|
d = LoadXRCDialog("ExportSPS");
|
|
// just verify text fields present
|
|
SafeXRCCTRL<wxTextCtrl>(d, "Title");
|
|
SafeXRCCTRL<wxTextCtrl>(d, "Description");
|
|
SafeXRCCTRL<wxTextCtrl>(d, "Notes");
|
|
d->Fit();
|
|
//// Emulation menu
|
|
#ifndef NO_LINK
|
|
d = LoadXRCDialog("NetLink");
|
|
#endif
|
|
wxRadioButton* rb;
|
|
#define getrbi(n, o, v) \
|
|
do { \
|
|
rb = SafeXRCCTRL<wxRadioButton>(d, n); \
|
|
rb->SetValidator(wxBoolIntValidator(&o, v)); \
|
|
} while (0)
|
|
#define getrbb(n, o) \
|
|
do { \
|
|
rb = SafeXRCCTRL<wxRadioButton>(d, n); \
|
|
rb->SetValidator(wxGenericValidator(&o)); \
|
|
} while (0)
|
|
#define getrbbr(n, o) \
|
|
do { \
|
|
rb = SafeXRCCTRL<wxRadioButton>(d, n); \
|
|
rb->SetValidator(wxBoolRevValidator(&o)); \
|
|
} while (0)
|
|
wxBoolEnValidator* benval;
|
|
wxBoolEnHandler* ben;
|
|
#define getbe(n, o, cv, t, wt) \
|
|
do { \
|
|
cv = SafeXRCCTRL<t>(d, n); \
|
|
cv->SetValidator(wxBoolEnValidator(&o)); \
|
|
benval = wxStaticCast(cv->GetValidator(), wxBoolEnValidator); \
|
|
static wxBoolEnHandler _ben; \
|
|
ben = &_ben; \
|
|
wx##wt##BoolEnHandlerConnect(cv, wxID_ANY, _ben); \
|
|
} while (0)
|
|
// brenval & friends are here just to allow yes/no radioboxes in place
|
|
// of checkboxes. A lot of work for little benefit.
|
|
wxBoolRevEnValidator* brenval;
|
|
#define getbre(n, o, cv, t, wt) \
|
|
do { \
|
|
cv = SafeXRCCTRL<t>(d, n); \
|
|
cv->SetValidator(wxBoolRevEnValidator(&o)); \
|
|
brenval = wxStaticCast(cv->GetValidator(), wxBoolRevEnValidator); \
|
|
wx##wt##BoolEnHandlerConnect(rb, wxID_ANY, *ben); \
|
|
} while (0)
|
|
#define addbe(n) \
|
|
do { \
|
|
ben->controls.push_back(n); \
|
|
benval->controls.push_back(n); \
|
|
} while (0)
|
|
#define addrbe(n) \
|
|
do { \
|
|
addbe(n); \
|
|
brenval->controls.push_back(n); \
|
|
} while (0)
|
|
#define addber(n, r) \
|
|
do { \
|
|
ben->controls.push_back(n); \
|
|
ben->reverse.push_back(r); \
|
|
benval->controls.push_back(n); \
|
|
benval->reverse.push_back(r); \
|
|
} while (0)
|
|
#define addrber(n, r) \
|
|
do { \
|
|
addber(n, r); \
|
|
brenval->controls.push_back(n); \
|
|
brenval->reverse.push_back(r); \
|
|
} while (0)
|
|
#define getrbbe(n, o) getbe(n, o, rb, wxRadioButton, RBE)
|
|
#define getrbbd(n, o) getbre(n, o, rb, wxRadioButton, RBD)
|
|
wxTextCtrl* tc;
|
|
#define gettc(n, o) \
|
|
do { \
|
|
tc = SafeXRCCTRL<wxTextCtrl>(d, n); \
|
|
tc->SetValidator(wxTextValidator(wxFILTER_NONE, &o)); \
|
|
} while (0)
|
|
#define getdtc(n, o) \
|
|
do { \
|
|
tc = SafeXRCCTRL<wxTextCtrl>(d, n); \
|
|
tc->SetValidator(wxPositiveDoubleValidator(&o)); \
|
|
} while (0)
|
|
#ifndef NO_LINK
|
|
{
|
|
net_link_handler.dlg = d;
|
|
net_link_handler.n_players = linkNumPlayers;
|
|
getrbbe("Server", net_link_handler.server);
|
|
getrbbd("Client", net_link_handler.server);
|
|
getlab("PlayersLab");
|
|
addrber(lab, false);
|
|
getrbi("Link2P", net_link_handler.n_players, 2);
|
|
addrber(rb, false);
|
|
getrbi("Link3P", net_link_handler.n_players, 3);
|
|
addrber(rb, false);
|
|
getrbi("Link4P", net_link_handler.n_players, 4);
|
|
addrber(rb, false);
|
|
getlab("ServerIPLab");
|
|
addrber(lab, true);
|
|
gettc("ServerIP", gopts.link_host);
|
|
addrber(tc, true);
|
|
wxWindow* okb = d->FindWindow(wxID_OK);
|
|
|
|
if (okb) // may be gone if style guidlines removed it
|
|
{
|
|
net_link_handler.okb = wxStaticCast(okb, wxButton);
|
|
d->Connect(XRCID("Server"), wxEVT_COMMAND_RADIOBUTTON_SELECTED,
|
|
wxCommandEventHandler(NetLink_t::ServerOKButton),
|
|
NULL, &net_link_handler);
|
|
d->Connect(XRCID("Client"), wxEVT_COMMAND_RADIOBUTTON_SELECTED,
|
|
wxCommandEventHandler(NetLink_t::ClientOKButton),
|
|
NULL, &net_link_handler);
|
|
}
|
|
|
|
// this should intercept wxID_OK before the dialog handler gets it
|
|
d->Connect(wxID_OK, wxEVT_COMMAND_BUTTON_CLICKED,
|
|
wxCommandEventHandler(NetLink_t::NetConnect),
|
|
NULL, &net_link_handler);
|
|
d->Fit();
|
|
}
|
|
#endif
|
|
d = LoadXRCDialog("CheatList");
|
|
{
|
|
cheat_list_handler.dlg = d;
|
|
d->SetEscapeId(wxID_OK);
|
|
wxCheckedListCtrl* cl;
|
|
cl = SafeXRCCTRL<wxCheckedListCtrl>(d, "Cheats");
|
|
|
|
if (!cl->Init())
|
|
throw std::runtime_error("Unable to initialize the Cheats dialog control from the builtin xrc file!");
|
|
|
|
cheat_list_handler.list = cl;
|
|
cl->SetValidator(CheatListFill());
|
|
cl->InsertColumn(0, _("Code"));
|
|
// can't just set font for whole column; must set in each
|
|
// individual item
|
|
wxFont of = cl->GetFont();
|
|
// of.SetFamily(wxFONTFAMILY_MODERN); // doesn't work (no font change)
|
|
wxFont f(of.GetPointSize(), wxFONTFAMILY_MODERN, of.GetStyle(),
|
|
of.GetWeight());
|
|
cheat_list_handler.item0.SetFont(f);
|
|
cheat_list_handler.item0.SetColumn(0);
|
|
cl->InsertColumn(1, _("Description"));
|
|
// too bad I can't just set the size to windowwidth - other cols
|
|
// default width is header width, but using following will probably
|
|
// make it 80 pixels wide regardless
|
|
// cl->SetColumnWidth(1, wxLIST_AUTOSIZE_USEHEADER);
|
|
cheat_list_handler.col1minw = cl->GetColumnWidth(1);
|
|
// on wxGTK, column 1 seems to inherit column 0's font regardless
|
|
// of requested font
|
|
cheat_list_handler.item1.SetFont(cl->GetFont());
|
|
cheat_list_handler.item1.SetColumn(1);
|
|
#if 0
|
|
// the ideal way to set col 0's width would be to use
|
|
// wxLIST_AUTOSIZE after setting value to a sample:
|
|
cheat_list_handler.item0.SetText(wxT("00000000 00000000"));
|
|
cl->InsertItem(cheat_list_handler.item0);
|
|
cl->SetColumnWidth(0, wxLIST_AUTOSIZE);
|
|
cl->RemoveItem(0);
|
|
#else
|
|
// however, the generic listctrl implementation uses the wrong
|
|
// font to determine width (window vs. item), and does not
|
|
// calculate the margins the same way in calculation vs. actual
|
|
// drawing. so calculate manually, using knowledge of underlying
|
|
// code. This is highly version-unportable, but better than using
|
|
// buggy wx code..
|
|
int w, h;
|
|
cl->GetImageList(wxIMAGE_LIST_SMALL)->GetSize(0, w, h);
|
|
w += 5; // IMAGE_MARGIN_IN_REPORT_MODE
|
|
// following is missing from wxLIST_AUTOSIZE
|
|
w += 8; // ??? subtracted from width avail for text
|
|
{
|
|
int charwidth, charheight;
|
|
wxClientDC dc(cl);
|
|
// following is item font instead of window font,
|
|
// and so is missing from wxLIST_AUTOSIZE
|
|
dc.SetFont(f);
|
|
dc.GetTextExtent(wxT('M'), &charwidth, &charheight);
|
|
w += (8 + 1 + 8) * charwidth;
|
|
}
|
|
cl->SetColumnWidth(0, w);
|
|
#endif
|
|
d->Connect(wxEVT_COMMAND_TOOL_CLICKED,
|
|
wxCommandEventHandler(CheatList_t::Tool),
|
|
NULL, &cheat_list_handler);
|
|
d->Connect(wxEVT_COMMAND_LIST_ITEM_CHECKED,
|
|
wxListEventHandler(CheatList_t::Check),
|
|
NULL, &cheat_list_handler);
|
|
d->Connect(wxEVT_COMMAND_LIST_ITEM_UNCHECKED,
|
|
wxListEventHandler(CheatList_t::UnCheck),
|
|
NULL, &cheat_list_handler);
|
|
d->Connect(wxEVT_COMMAND_LIST_ITEM_ACTIVATED,
|
|
wxListEventHandler(CheatList_t::Edit),
|
|
NULL, &cheat_list_handler);
|
|
d->Fit();
|
|
}
|
|
d = LoadXRCDialog("CheatEdit");
|
|
wxChoice* ch;
|
|
{
|
|
// d->Reparent(cheat_list_handler.dlg); // broken
|
|
ch = GetValidatedChild<wxChoice, wxGenericValidator>(d, "Type", wxGenericValidator(&cheat_list_handler.ce_type));
|
|
cheat_list_handler.ce_type_ch = ch;
|
|
gettc("Desc", cheat_list_handler.ce_desc);
|
|
tc->SetMaxLength(sizeof(cheatsList[0].desc) - 1);
|
|
gettc("Codes", cheat_list_handler.ce_codes);
|
|
cheat_list_handler.ce_codes_tc = tc;
|
|
d->Fit();
|
|
}
|
|
d = LoadXRCDialog("CheatCreate");
|
|
{
|
|
cheat_find_handler.dlg = d;
|
|
d->SetEscapeId(wxID_OK);
|
|
CheatListCtrl* list;
|
|
list = SafeXRCCTRL<CheatListCtrl>(d, "CheatList");
|
|
cheat_find_handler.list = list;
|
|
list->SetValidator(CheatFindFill());
|
|
list->InsertColumn(0, _("Address"));
|
|
list->InsertColumn(1, _("Old Value"));
|
|
list->InsertColumn(2, _("New Value"));
|
|
getrbi("EQ", cheat_find_handler.op, SEARCH_EQ);
|
|
getrbi("NE", cheat_find_handler.op, SEARCH_NE);
|
|
getrbi("LT", cheat_find_handler.op, SEARCH_LT);
|
|
getrbi("LE", cheat_find_handler.op, SEARCH_LE);
|
|
getrbi("GT", cheat_find_handler.op, SEARCH_GT);
|
|
getrbi("GE", cheat_find_handler.op, SEARCH_GE);
|
|
#define cf_make_update() \
|
|
rb->Connect(wxEVT_COMMAND_RADIOBUTTON_SELECTED, \
|
|
wxCommandEventHandler(CheatFind_t::UpdateView), \
|
|
NULL, &cheat_find_handler)
|
|
getrbi("Size8", cheat_find_handler.size, BITS_8);
|
|
cf_make_update();
|
|
getrbi("Size16", cheat_find_handler.size, BITS_16);
|
|
cf_make_update();
|
|
getrbi("Size32", cheat_find_handler.size, BITS_32);
|
|
cf_make_update();
|
|
getrbi("Signed", cheat_find_handler.fmt, CFVFMT_SD);
|
|
cf_make_update();
|
|
getrbi("Unsigned", cheat_find_handler.fmt, CFVFMT_UD);
|
|
cf_make_update();
|
|
getrbi("Hexadecimal", cheat_find_handler.fmt, CFVFMT_UH);
|
|
cf_make_update();
|
|
#define cf_make_valen() \
|
|
rb->Connect(wxEVT_COMMAND_RADIOBUTTON_SELECTED, \
|
|
wxCommandEventHandler(CheatFind_t::EnableVal), \
|
|
NULL, &cheat_find_handler)
|
|
getrbi("OldValue", cheat_find_handler.valsrc, 1);
|
|
cf_make_valen();
|
|
cheat_find_handler.old_rb = rb;
|
|
rb->Disable();
|
|
getrbi("SpecificValue", cheat_find_handler.valsrc, 0);
|
|
cf_make_valen();
|
|
cheat_find_handler.val_rb = rb;
|
|
gettc("Value", cheat_find_handler.val_s);
|
|
cheat_find_handler.val_tc = tc;
|
|
wxStaticCast(tc->GetValidator(), wxTextValidator)->SetStyle(wxFILTER_INCLUDE_CHAR_LIST);
|
|
#define cf_button(n, f) \
|
|
d->Connect(XRCID(n), wxEVT_COMMAND_BUTTON_CLICKED, \
|
|
wxCommandEventHandler(CheatFind_t::f), \
|
|
NULL, &cheat_find_handler);
|
|
#define cf_enbutton(n, v) \
|
|
do { \
|
|
cheat_find_handler.v = SafeXRCCTRL<wxButton>(d, n); \
|
|
cheat_find_handler.v->Disable(); \
|
|
} while (0)
|
|
cf_button("Search", Search);
|
|
cf_button("Update", UpdateVals);
|
|
cf_enbutton("Update", update_b);
|
|
cf_button("Clear", ResetSearch);
|
|
cf_enbutton("Clear", clear_b);
|
|
cf_button("AddCheat", AddCheatB);
|
|
cf_enbutton("AddCheat", add_b);
|
|
d->Connect(wxEVT_COMMAND_LIST_ITEM_ACTIVATED,
|
|
wxListEventHandler(CheatFind_t::AddCheatL),
|
|
NULL, &cheat_find_handler);
|
|
d->Connect(wxEVT_COMMAND_LIST_ITEM_SELECTED,
|
|
wxListEventHandler(CheatFind_t::Select),
|
|
NULL, &cheat_find_handler);
|
|
d->Fit();
|
|
}
|
|
d = LoadXRCDialog("CheatAdd");
|
|
{
|
|
// d->Reparent(cheat_find_handler.dlg); // broken
|
|
gettc("Desc", cheat_find_handler.ca_desc);
|
|
tc->SetMaxLength(sizeof(cheatsList[0].desc) - 1);
|
|
gettc("Value", cheat_find_handler.ca_val);
|
|
cheat_find_handler.ca_val_tc = tc;
|
|
// MFC interface used this for cheat list's generic code adder as well,
|
|
// and made format selectable in interface. I think the plain
|
|
// interface is good enough, even though the format for GB cheats
|
|
// is non-obvious. Therefore, the format is now just a read-only
|
|
// field.
|
|
getlab("Format");
|
|
cheat_find_handler.ca_fmt = lab;
|
|
getlab("Address");
|
|
cheat_find_handler.ca_addr = lab;
|
|
d->Fit();
|
|
}
|
|
//// config menu
|
|
d = LoadXRCDialog("GeneralConfig");
|
|
wxCheckBox* cb;
|
|
#define getcbb(n, o) \
|
|
do { \
|
|
cb = SafeXRCCTRL<wxCheckBox>(d, n); \
|
|
cb->SetValidator(wxGenericValidator(&o)); \
|
|
} while (0)
|
|
#define getcbi(n, o) \
|
|
do { \
|
|
cb = SafeXRCCTRL<wxCheckBox>(d, n); \
|
|
cb->SetValidator(wxBoolIntValidator(&o, 1)); \
|
|
} while (0)
|
|
wxSpinCtrl* sc;
|
|
#define getsc(n, o) \
|
|
do { \
|
|
sc = SafeXRCCTRL<wxSpinCtrl>(d, n); \
|
|
sc->SetValidator(wxGenericValidator(&o)); \
|
|
} while (0)
|
|
{
|
|
// Online Auto Update check frequency
|
|
getrbi("UpdateNever", gopts.onlineupdates, 0);
|
|
getrbi("UpdateDaily", gopts.onlineupdates, 1);
|
|
getrbi("UpdateWeekly", gopts.onlineupdates, 7);
|
|
getrbi("PNG", captureFormat, 0);
|
|
getrbi("BMP", captureFormat, 1);
|
|
getsc("RewindInterval", gopts.rewind_interval);
|
|
getsc("Throttle", throttle);
|
|
throttle_ctrl.thr = sc;
|
|
throttle_ctrl.thrsel = SafeXRCCTRL<wxChoice>(d, "ThrottleSel");
|
|
throttle_ctrl.thr->Connect(wxEVT_COMMAND_SPINCTRL_UPDATED,
|
|
wxSpinEventHandler(ThrottleCtrl_t::SetThrottleSel),
|
|
NULL, &throttle_ctrl);
|
|
throttle_ctrl.thrsel->Connect(wxEVT_COMMAND_CHOICE_SELECTED,
|
|
wxCommandEventHandler(ThrottleCtrl_t::SetThrottle),
|
|
NULL, &throttle_ctrl);
|
|
d->Connect(wxEVT_SHOW, wxShowEventHandler(ThrottleCtrl_t::Init),
|
|
NULL, &throttle_ctrl);
|
|
d->Fit();
|
|
}
|
|
#define getcbbe(n, o) getbe(n, o, cb, wxCheckBox, CB)
|
|
wxBoolIntEnValidator* bienval;
|
|
#define getbie(n, o, v, cv, t, wt) \
|
|
do { \
|
|
cv = SafeXRCCTRL<t>(d, n); \
|
|
cv->SetValidator(wxBoolIntEnValidator(&o, v, v)); \
|
|
bienval = wxStaticCast(cv->GetValidator(), wxBoolIntEnValidator); \
|
|
static wxBoolEnHandler _ben; \
|
|
ben = &_ben; \
|
|
wx##wt##BoolEnHandlerConnect(cv, wxID_ANY, _ben); \
|
|
} while (0)
|
|
#define addbie(n) \
|
|
do { \
|
|
ben->controls.push_back(n); \
|
|
bienval->controls.push_back(n); \
|
|
} while (0)
|
|
#define addbier(n, r) \
|
|
do { \
|
|
ben->controls.push_back(n); \
|
|
ben->reverse.push_back(r); \
|
|
bienval->controls.push_back(n); \
|
|
bienval->reverse.push_back(r); \
|
|
} while (0)
|
|
#define getcbie(n, o, v) getbie(n, o, v, cb, wxCheckBox, CB)
|
|
wxFilePickerCtrl* fp;
|
|
#define getfp(n, o) \
|
|
do { \
|
|
fp = SafeXRCCTRL<wxFilePickerCtrl>(d, n); \
|
|
fp->SetValidator(wxFileDirPickerValidator(&o)); \
|
|
} while (0)
|
|
d = LoadXRCropertySheetDialog("GameBoyConfig");
|
|
{
|
|
/// System and Peripherals
|
|
ch = GetValidatedChild<wxChoice, wxGenericValidator>(d, "System", wxGenericValidator(&gbEmulatorType));
|
|
// "Display borders" corresponds to 2 variables, so it is handled
|
|
// in command handler. Plus making changes might require resizing
|
|
// game area. Validation only here.
|
|
SafeXRCCTRL<wxChoice>(d, "Borders");
|
|
/// Boot ROM
|
|
getfp("BootRom", gopts.gb_bios);
|
|
getlab("BootRomLab");
|
|
getfp("CBootRom", gopts.gbc_bios);
|
|
getlab("CBootRomLab");
|
|
/// Custom Colors
|
|
//getcbi("Color", gbColorOption);
|
|
wxFarRadio* r = NULL;
|
|
|
|
for (int i = 0; i < 3; i++) {
|
|
wxString pn;
|
|
// NOTE: wx2.9.1 behaves differently for referenced nodes
|
|
// than 2.8! Unless there is an actual child node, the ID field
|
|
// will not be overwritten. This means that there should be a
|
|
// dummy child node (e.g. position=(0,0)). If you get
|
|
// "Unable to load dialog GameBoyConfig from resources", this is
|
|
// probably the reason.
|
|
pn.Printf(wxT("cp%d"), i + 1);
|
|
wxWindow* w = SafeXRCCTRL<wxWindow>(d, pn);
|
|
GBColorConfigHandler[i].p = w;
|
|
GBColorConfigHandler[i].pno = i;
|
|
wxFarRadio* cb = SafeXRCCTRL<wxFarRadio>(w, "UsePalette");
|
|
|
|
if (r)
|
|
cb->SetGroup(r);
|
|
else
|
|
r = cb;
|
|
|
|
cb->SetValidator(wxBoolIntValidator(&gbPaletteOption, i));
|
|
ch = SafeXRCCTRL<wxChoice>(w, "ColorSet");
|
|
GBColorConfigHandler[i].c = ch;
|
|
|
|
for (int j = 0; j < 8; j++) {
|
|
wxString s;
|
|
s.Printf(wxT("Color%d"), j);
|
|
wxColourPickerCtrl* cp = SafeXRCCTRL<wxColourPickerCtrl>(w, s);
|
|
GBColorConfigHandler[i].cp[j] = cp;
|
|
cp->SetValidator(wxColorValidator(&systemGbPalette[i * 8 + j]));
|
|
}
|
|
|
|
w->Connect(wxEVT_COMMAND_CHOICE_SELECTED,
|
|
wxCommandEventHandler(GBColorConfig_t::ColorSel),
|
|
NULL, &GBColorConfigHandler[i]);
|
|
w->Connect(XRCID("Reset"), wxEVT_COMMAND_BUTTON_CLICKED,
|
|
wxCommandEventHandler(GBColorConfig_t::ColorReset),
|
|
NULL, &GBColorConfigHandler[i]);
|
|
w->Connect(wxID_ANY, wxEVT_COMMAND_COLOURPICKER_CHANGED,
|
|
wxCommandEventHandler(GBColorConfig_t::ColorButton),
|
|
NULL, &GBColorConfigHandler[i]);
|
|
}
|
|
|
|
d->Fit();
|
|
}
|
|
d = LoadXRCropertySheetDialog("GameBoyAdvanceConfig");
|
|
{
|
|
/// System and peripherals
|
|
ch = GetValidatedChild<wxChoice, wxGenericValidator>(d, "SaveType", wxGenericValidator(&cpuSaveType));
|
|
BatConfigHandler.type = ch;
|
|
ch = GetValidatedChild<wxChoice, wxGenericValidator>(d, "FlashSize", wxGenericValidator(&winFlashSize));
|
|
BatConfigHandler.size = ch;
|
|
d->Connect(XRCID("SaveType"), wxEVT_COMMAND_CHOICE_SELECTED,
|
|
wxCommandEventHandler(BatConfig_t::ChangeType),
|
|
NULL, &BatConfigHandler);
|
|
#define getgbaw(n) \
|
|
do { \
|
|
wxWindow* w = d->FindWindow(XRCID(n)); \
|
|
CheckThrowXRCError(w, n); \
|
|
w->SetValidator(GBACtrlEnabler()); \
|
|
} while (0)
|
|
getgbaw("Detect");
|
|
d->Connect(XRCID("Detect"), wxEVT_COMMAND_BUTTON_CLICKED,
|
|
wxCommandEventHandler(BatConfig_t::Detect),
|
|
NULL, &BatConfigHandler);
|
|
/// Boot ROM
|
|
getfp("BootRom", gopts.gba_bios);
|
|
getlab("BootRomLab");
|
|
/// Game Overrides
|
|
getgbaw("GameSettings");
|
|
// the rest must be filled in by command handler; just validate
|
|
SafeXRCCTRL<wxTextCtrl>(d, "Comment");
|
|
SafeXRCCTRL<wxChoice>(d, "OvRTC");
|
|
SafeXRCCTRL<wxChoice>(d, "OvSaveType");
|
|
SafeXRCCTRL<wxChoice>(d, "OvFlashSize");
|
|
SafeXRCCTRL<wxChoice>(d, "OvMirroring");
|
|
d->Fit();
|
|
}
|
|
d = LoadXRCropertySheetDialog("DisplayConfig");
|
|
{
|
|
/// Speed
|
|
// AutoSkip/FrameSkip are 2 controls for 1 value. Needs post-process
|
|
// to ensure checkbox not ignored
|
|
getsc("FrameSkip", frameSkip);
|
|
getlab("FrameSkipLab");
|
|
int fs = frameSkip;
|
|
|
|
if (fs >= 0)
|
|
systemFrameSkip = fs;
|
|
|
|
/// On-Screen Display
|
|
ch = GetValidatedChild<wxChoice, wxGenericValidator>(d, "SpeedIndicator", wxGenericValidator(&showSpeed));
|
|
/// Zoom
|
|
getdtc("DefaultScale", gopts.video_scale);
|
|
// this was a choice, but I'd rather not have to make an off-by-one
|
|
// validator just for this, and spinctrl is good enough.
|
|
getsc("MaxScale", maxScale);
|
|
/// Basic
|
|
getrbi("OutputSimple", gopts.render_method, RND_SIMPLE);
|
|
getrbi("OutputQuartz2D", gopts.render_method, RND_QUARTZ2D);
|
|
#if !defined(__WXMAC__)
|
|
rb->Hide();
|
|
#endif
|
|
getrbi("OutputOpenGL", gopts.render_method, RND_OPENGL);
|
|
#ifdef NO_OGL
|
|
rb->Hide();
|
|
#endif
|
|
#ifdef __WXGTK__
|
|
// wxGLCanvas segfaults on Wayland
|
|
if (wxGetApp().UsingWayland()) {
|
|
rb->Hide();
|
|
}
|
|
#endif
|
|
getrbi("OutputDirect3D", gopts.render_method, RND_DIRECT3D);
|
|
#if !defined(__WXMSW__) || defined(NO_D3D) || 1 // not implemented
|
|
rb->Hide();
|
|
#endif
|
|
ch = GetValidatedChild<wxChoice, wxGenericValidator>(d, "Filter", wxGenericValidator(&gopts.filter));
|
|
// these two are filled and/or hidden at dialog load time
|
|
wxControl* pll;
|
|
wxChoice* pl;
|
|
pll = SafeXRCCTRL<wxControl>(d, "PluginLab");
|
|
pl = SafeXRCCTRL<wxChoice>(d, "Plugin");
|
|
pll->SetValidator(PluginEnabler());
|
|
pl->SetValidator(PluginListFiller(d, pll, ch));
|
|
PluginEnableHandler.lab = pll;
|
|
PluginEnableHandler.ch = pl;
|
|
ch->Connect(wxEVT_COMMAND_CHOICE_SELECTED,
|
|
wxCommandEventHandler(PluginEnable_t::ToggleChoice),
|
|
NULL, &PluginEnableHandler);
|
|
ch = GetValidatedChild<wxChoice, wxGenericValidator>(d, "IFB", wxGenericValidator(&gopts.ifb));
|
|
d->Fit();
|
|
}
|
|
d = LoadXRCropertySheetDialog("SoundConfig");
|
|
wxSlider* sl;
|
|
#define getsl(n, o) \
|
|
do { \
|
|
sl = SafeXRCCTRL<wxSlider>(d, n); \
|
|
sl->SetValidator(wxGenericValidator(&o)); \
|
|
} while (0)
|
|
{
|
|
/// Basic
|
|
getsl("Volume", gopts.sound_vol);
|
|
sound_config_handler.vol = sl;
|
|
d->Connect(XRCID("Volume100"), wxEVT_COMMAND_BUTTON_CLICKED,
|
|
wxCommandEventHandler(SoundConfig_t::FullVol),
|
|
NULL, &sound_config_handler);
|
|
ch = GetValidatedChild<wxChoice, wxGenericValidator>(d, "Rate", wxGenericValidator(&gopts.sound_qual));
|
|
/// Advanced
|
|
#define audapi_rb(n, v) \
|
|
do { \
|
|
getrbi(n, gopts.audio_api, v); \
|
|
rb->Connect(wxEVT_COMMAND_RADIOBUTTON_SELECTED, \
|
|
wxCommandEventHandler(SoundConfig_t::SetAPI), \
|
|
NULL, &sound_config_handler); \
|
|
} while (0)
|
|
audapi_rb("SDL", AUD_SDL);
|
|
audapi_rb("OpenAL", AUD_OPENAL);
|
|
#ifdef NO_OAL
|
|
rb->Hide();
|
|
#endif
|
|
audapi_rb("DirectSound", AUD_DIRECTSOUND);
|
|
#ifndef __WXMSW__
|
|
rb->Hide();
|
|
#endif
|
|
audapi_rb("XAudio2", AUD_XAUDIO2);
|
|
#if !defined(__WXMSW__) || defined(NO_XAUDIO2)
|
|
rb->Hide();
|
|
#endif
|
|
audapi_rb("Faudio", AUD_FAUDIO);
|
|
#ifdef NO_FAUDIO
|
|
rb->Hide();
|
|
#endif
|
|
sound_config_handler.dev = SafeXRCCTRL<wxChoice>(d, "Device");
|
|
sound_config_handler.dev->SetValidator(SoundConfigLoad());
|
|
getcbb("Upmix", gopts.upmix);
|
|
sound_config_handler.umix = cb;
|
|
#if !defined(__WXMSW__) || defined(NO_XAUDIO2)
|
|
cb->Hide();
|
|
#endif
|
|
getcbb("HWAccel", gopts.dsound_hw_accel);
|
|
sound_config_handler.hwacc = cb;
|
|
#ifndef __WXMSW__
|
|
cb->Hide();
|
|
#endif
|
|
getsl("Buffers", gopts.audio_buffers);
|
|
sound_config_handler.bufs = sl;
|
|
getlab("BuffersInfo");
|
|
sound_config_handler.bufinfo = lab;
|
|
sl->Connect(wxEVT_SCROLL_CHANGED,
|
|
wxCommandEventHandler(SoundConfig_t::AdjustFramesEv),
|
|
NULL, &sound_config_handler);
|
|
sl->Connect(wxEVT_SCROLL_THUMBTRACK,
|
|
wxCommandEventHandler(SoundConfig_t::AdjustFramesEv),
|
|
NULL, &sound_config_handler);
|
|
sound_config_handler.AdjustFrames(10);
|
|
/// Game Boy
|
|
SafeXRCCTRL<wxPanel>(d, "GBEnhanceSoundDep");
|
|
getsl("GBEcho", gopts.gb_echo);
|
|
getsl("GBStereo", gopts.gb_stereo);
|
|
/// Game Boy Advance
|
|
getsl("GBASoundFiltering", gopts.gba_sound_filter);
|
|
d->Fit();
|
|
}
|
|
wxDirPickerCtrl* dp;
|
|
#define getdp(n, o) \
|
|
do { \
|
|
dp = SafeXRCCTRL<wxDirPickerCtrl>(d, n); \
|
|
dp->SetValidator(wxFileDirPickerValidator(&o)); \
|
|
} while (0)
|
|
d = LoadXRCDialog("DirectoriesConfig");
|
|
{
|
|
getdp("GBARoms", gopts.gba_rom_dir);
|
|
getdp("GBRoms", gopts.gb_rom_dir);
|
|
getdp("GBCRoms", gopts.gbc_rom_dir);
|
|
getdp("BatSaves", gopts.battery_dir);
|
|
getdp("StateSaves", gopts.state_dir);
|
|
getdp("Screenshots", gopts.scrshot_dir);
|
|
getdp("Recordings", gopts.recording_dir);
|
|
d->Fit();
|
|
}
|
|
wxDialog* joyDialog = LoadXRCropertySheetDialog("JoypadConfig");
|
|
wxFarRadio* r = 0;
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
wxString pn;
|
|
// NOTE: wx2.9.1 behaves differently for referenced nodes
|
|
// than 2.8! Unless there is an actual child node, the ID field
|
|
// will not be overwritten. This means that there should be a
|
|
// dummy child node (e.g. position=(0,0)). If you get
|
|
// "Unable to load dialog JoypadConfig from resources", this is
|
|
// probably the reason.
|
|
pn.Printf(wxT("joy%d"), i + 1);
|
|
wxWindow* w = SafeXRCCTRL<wxWindow>(joyDialog, pn);
|
|
wxFarRadio* cb;
|
|
cb = SafeXRCCTRL<wxFarRadio>(w, "DefaultConfig");
|
|
|
|
if (r)
|
|
cb->SetGroup(r);
|
|
else
|
|
r = cb;
|
|
|
|
cb->SetValidator(wxBoolIntValidator(&gopts.default_stick, i + 1));
|
|
wxWindow *prev = NULL, *prevp = NULL;
|
|
|
|
for (int j = 0; j < NUM_KEYS; j++) {
|
|
wxJoyKeyTextCtrl* tc = XRCCTRL_D(*w, joynames[j], wxJoyKeyTextCtrl);
|
|
CheckThrowXRCError(tc, joynames[j]);
|
|
wxWindow* p = tc->GetParent();
|
|
|
|
if (p == prevp)
|
|
tc->MoveAfterInTabOrder(prev);
|
|
|
|
prev = tc;
|
|
prevp = p;
|
|
tc->SetValidator(wxJoyKeyValidator(&gopts.joykey_bindings[i][j]));
|
|
}
|
|
|
|
JoyPadConfigHandler[i].p = w;
|
|
w->Connect(XRCID("Defaults"), wxEVT_COMMAND_BUTTON_CLICKED,
|
|
wxCommandEventHandler(JoyPadConfig_t::JoypadConfigButtons),
|
|
NULL, &JoyPadConfigHandler[i]);
|
|
w->Connect(XRCID("Clear"), wxEVT_COMMAND_BUTTON_CLICKED,
|
|
wxCommandEventHandler(JoyPadConfig_t::JoypadConfigButtons),
|
|
NULL, &JoyPadConfigHandler[i]);
|
|
joyDialog->Fit();
|
|
}
|
|
|
|
#ifndef NO_LINK
|
|
d = LoadXRCDialog("LinkConfig");
|
|
{
|
|
getlab("LinkTimeoutLab");
|
|
addbe(lab);
|
|
getsc("LinkTimeout", linkTimeout);
|
|
addbe(sc);
|
|
d->Fit();
|
|
}
|
|
#endif
|
|
d = LoadXRCDialog("AccelConfig");
|
|
{
|
|
wxTreeCtrl* tc;
|
|
tc = SafeXRCCTRL<wxTreeCtrl>(d, "Commands");
|
|
accel_config_handler.tc = tc;
|
|
wxControlWithItems* lb;
|
|
lb = SafeXRCCTRL<wxControlWithItems>(d, "Current");
|
|
accel_config_handler.lb = lb;
|
|
accel_config_handler.asb = SafeXRCCTRL<wxButton>(d, "Assign");
|
|
accel_config_handler.remb = SafeXRCCTRL<wxButton>(d, "Remove");
|
|
accel_config_handler.key = SafeXRCCTRL<wxKeyTextCtrl>(d, "Shortcut");
|
|
accel_config_handler.curas = SafeXRCCTRL<wxControl>(d, "AlreadyThere");
|
|
accel_config_handler.key->MoveBeforeInTabOrder(accel_config_handler.asb);
|
|
accel_config_handler.key->SetMultikey(0);
|
|
accel_config_handler.key->SetClearable(false);
|
|
wxTreeItemId rid = tc->AddRoot(wxT("root"));
|
|
|
|
if (menubar) {
|
|
wxTreeItemId mid = tc->AppendItem(rid, _("Menu commands"));
|
|
|
|
for (int i = 0; i < menubar->GetMenuCount(); i++) {
|
|
#if wxCHECK_VERSION(2, 8, 8)
|
|
wxTreeItemId id = tc->AppendItem(mid, menubar->GetMenuLabelText(i));
|
|
#else
|
|
// 2.8.4 has no equivalent for GetMenuLabelText()
|
|
wxString txt = menubar->GetMenuLabel(i);
|
|
txt.Replace(wxT("&"), wxT(""));
|
|
wxTreeItemId id = tc->AppendItem(mid, txt);
|
|
#endif
|
|
add_menu_accels(tc, id, menubar->GetMenu(i));
|
|
}
|
|
}
|
|
|
|
wxTreeItemId oid;
|
|
int noop_id = XRCID("NOOP");
|
|
|
|
for (int i = 0; i < ncmds; i++) {
|
|
if (cmdtab[i].mi || (recent && cmdtab[i].cmd_id >= wxID_FILE1 && cmdtab[i].cmd_id <= wxID_FILE10) || cmdtab[i].cmd_id == noop_id)
|
|
continue;
|
|
|
|
if (!oid.IsOk())
|
|
oid = tc->AppendItem(rid, _("Other commands"));
|
|
|
|
TreeInt* val = new TreeInt(i);
|
|
tc->AppendItem(oid, cmdtab[i].name, -1, -1, val);
|
|
}
|
|
|
|
tc->ExpandAll();
|
|
// FIXME: make this actually show the entire line w/o scrolling
|
|
// BestSize cuts off on rhs; MaxSize is completely invalid
|
|
wxSize sz = tc->GetBestSize();
|
|
|
|
if (sz.GetHeight() > 200)
|
|
sz.SetHeight(200);
|
|
|
|
tc->SetSize(sz);
|
|
sz.SetWidth(-1); // maybe allow it to become bigger
|
|
tc->SetSizeHints(sz, sz);
|
|
int w, h;
|
|
lb->GetTextExtent(wxT("CTRL-ALT-SHIFT-ENTER"), &w, &h);
|
|
sz.Set(w, h);
|
|
lb->SetMinSize(sz);
|
|
sz.Set(0, 0);
|
|
wxControl* curas = accel_config_handler.curas;
|
|
|
|
for (int i = 0; i < ncmds; i++) {
|
|
wxString labs;
|
|
treeid_to_name(i, labs, tc, tc->GetRootItem());
|
|
curas->GetTextExtent(labs, &w, &h);
|
|
|
|
if (w > sz.GetWidth())
|
|
sz.SetWidth(w);
|
|
|
|
if (h > sz.GetHeight())
|
|
sz.SetHeight(h);
|
|
}
|
|
|
|
curas->SetSize(sz);
|
|
curas->SetSizeHints(sz);
|
|
tc->Connect(wxEVT_COMMAND_TREE_SEL_CHANGING,
|
|
wxTreeEventHandler(AccelConfig_t::CommandSel),
|
|
NULL, &accel_config_handler);
|
|
tc->Connect(wxEVT_COMMAND_TREE_SEL_CHANGED,
|
|
wxTreeEventHandler(AccelConfig_t::CommandSel),
|
|
NULL, &accel_config_handler);
|
|
d->Connect(wxEVT_SHOW, wxShowEventHandler(AccelConfig_t::Init),
|
|
NULL, &accel_config_handler);
|
|
d->Connect(wxID_OK, wxEVT_COMMAND_BUTTON_CLICKED,
|
|
wxCommandEventHandler(AccelConfig_t::Set),
|
|
NULL, &accel_config_handler);
|
|
d->Connect(XRCID("Assign"), wxEVT_COMMAND_BUTTON_CLICKED,
|
|
wxCommandEventHandler(AccelConfig_t::Assign),
|
|
NULL, &accel_config_handler);
|
|
d->Connect(XRCID("Remove"), wxEVT_COMMAND_BUTTON_CLICKED,
|
|
wxCommandEventHandler(AccelConfig_t::Remove),
|
|
NULL, &accel_config_handler);
|
|
d->Connect(XRCID("ResetAll"), wxEVT_COMMAND_BUTTON_CLICKED,
|
|
wxCommandEventHandler(AccelConfig_t::ResetAll),
|
|
NULL, &accel_config_handler);
|
|
lb->Connect(wxEVT_COMMAND_LISTBOX_SELECTED,
|
|
wxCommandEventHandler(AccelConfig_t::KeySel),
|
|
NULL, &accel_config_handler);
|
|
d->Connect(XRCID("Shortcut"), wxEVT_COMMAND_TEXT_UPDATED,
|
|
wxCommandEventHandler(AccelConfig_t::CheckKey),
|
|
NULL, &accel_config_handler);
|
|
d->Fit();
|
|
}
|
|
} catch (std::exception& e) {
|
|
wxLogError(wxString::FromUTF8(e.what()));
|
|
return false;
|
|
}
|
|
|
|
//// Debug menu
|
|
// actually, the viewers can be instantiated multiple times.
|
|
// since they're for debugging, it's probably OK to just detect errors
|
|
// at popup time.
|
|
// The only one that can only be popped up once is logging, so allocate
|
|
// and check it already.
|
|
logdlg = new LogDialog;
|
|
// activate OnDropFile event handler
|
|
#if !defined(__WXGTK__) || wxCHECK_VERSION(2, 8, 10)
|
|
// may not actually do anything, but verfied to work w/ Linux/Nautilus
|
|
DragAcceptFiles(true);
|
|
#endif
|
|
|
|
// delayed fullscreen
|
|
if (wxGetApp().pending_fullscreen)
|
|
panel->ShowFullScreen(true);
|
|
|
|
MainFrame* mf = wxGetApp().frame;
|
|
|
|
if (gopts.statusbar)
|
|
mf->GetStatusBar()->Show();
|
|
else
|
|
mf->GetStatusBar()->Hide();
|
|
|
|
if (gopts.keep_on_top)
|
|
mf->SetWindowStyle(mf->GetWindowStyle() | wxSTAY_ON_TOP);
|
|
else
|
|
mf->SetWindowStyle(mf->GetWindowStyle() & ~wxSTAY_ON_TOP);
|
|
|
|
#ifndef NO_LINK
|
|
LinkMode linkMode = GetConfiguredLinkMode();
|
|
|
|
if (linkMode == LINK_GAMECUBE_DOLPHIN) {
|
|
bool isv = !gopts.link_host.empty();
|
|
|
|
if (isv) {
|
|
isv = SetLinkServerHost(gopts.link_host.mb_str());
|
|
}
|
|
|
|
if (!isv) {
|
|
wxLogError(_("JoyBus host invalid; disabling"));
|
|
} else {
|
|
linkMode = LINK_DISCONNECTED;
|
|
}
|
|
}
|
|
|
|
ConnectionState linkState = InitLink(linkMode);
|
|
|
|
if (linkState != LINK_OK) {
|
|
CloseLink();
|
|
}
|
|
|
|
if (GetLinkMode() != LINK_DISCONNECTED) {
|
|
cmd_enable |= CMDEN_LINK_ANY;
|
|
SetLinkTimeout(linkTimeout);
|
|
EnableSpeedHacks(linkHacks);
|
|
}
|
|
|
|
EnableNetworkMenu();
|
|
#endif
|
|
enable_menus();
|
|
panel->SetFrameTitle();
|
|
// All OK; activate idle loop
|
|
panel->SetExtraStyle(panel->GetExtraStyle() | wxWS_EX_PROCESS_IDLE);
|
|
return true;
|
|
}
|