CheatManager/ISOProperties: Refactor ActionReplay tabs as a class

Refactor Action Replay code into its own class like Gecko Codes.
This commit is contained in:
EmptyChaos
2016-07-23 01:05:04 +00:00
parent def0b42fb7
commit 00a4404045
17 changed files with 706 additions and 487 deletions

View File

@ -0,0 +1,193 @@
// Copyright 2008 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include <string>
#include <utility>
#include <vector>
#include <wx/button.h>
#include <wx/dialog.h>
#include <wx/gbsizer.h>
#include <wx/msgdlg.h>
#include <wx/sizer.h>
#include <wx/statbox.h>
#include <wx/stattext.h>
#include <wx/stockitem.h>
#include <wx/textctrl.h>
#include "Common/CommonTypes.h"
#include "Common/StringUtil.h"
#include "Core/ARDecrypt.h"
#include "Core/ActionReplay.h"
#include "DolphinWX/Cheats/ARCodeAddEdit.h"
#include "DolphinWX/WxUtils.h"
ARCodeAddEdit::ARCodeAddEdit(ActionReplay::ARCode code, wxWindow* parent, wxWindowID id,
const wxString& title, const wxPoint& position, const wxSize& size,
long style)
: wxDialog(parent, id, title, position, size, style), m_code(std::move(code))
{
CreateGUI();
}
void ARCodeAddEdit::CreateGUI()
{
const int space10 = FromDIP(10);
const int space5 = FromDIP(5);
wxBoxSizer* sEditCheat = new wxBoxSizer(wxVERTICAL);
wxStaticBoxSizer* sbEntry = new wxStaticBoxSizer(wxVERTICAL, this, _("Cheat Code"));
wxGridBagSizer* sgEntry = new wxGridBagSizer(space10, space10);
wxStaticText* lbl_cheat_name = new wxStaticText(sbEntry->GetStaticBox(), wxID_ANY, _("Name:"));
wxStaticText* lbl_cheat_codes = new wxStaticText(sbEntry->GetStaticBox(), wxID_ANY, _("Code:"));
m_txt_cheat_name = new wxTextCtrl(sbEntry->GetStaticBox(), wxID_ANY, wxEmptyString);
m_txt_cheat_name->SetValue(StrToWxStr(m_code.name));
m_cheat_codes =
new wxTextCtrl(sbEntry->GetStaticBox(), wxID_ANY, wxEmptyString, wxDefaultPosition,
wxDLG_UNIT(this, wxSize(240, 128)), wxTE_MULTILINE);
for (const auto& op : m_code.ops)
m_cheat_codes->AppendText(wxString::Format("%08X %08X\n", op.cmd_addr, op.value));
{
wxFont font{m_cheat_codes->GetFont()};
font.SetFamily(wxFONTFAMILY_TELETYPE);
#ifdef _WIN32
// Windows uses Courier New for monospace even though there are better fonts.
font.SetFaceName("Consolas");
#endif
m_cheat_codes->SetFont(font);
}
sgEntry->Add(lbl_cheat_name, wxGBPosition(0, 0), wxGBSpan(1, 1), wxALIGN_CENTER);
sgEntry->Add(lbl_cheat_codes, wxGBPosition(1, 0), wxGBSpan(1, 1), wxALIGN_CENTER);
sgEntry->Add(m_txt_cheat_name, wxGBPosition(0, 1), wxGBSpan(1, 1), wxEXPAND);
sgEntry->Add(m_cheat_codes, wxGBPosition(1, 1), wxGBSpan(1, 1), wxEXPAND);
sgEntry->AddGrowableCol(1);
sgEntry->AddGrowableRow(1);
sbEntry->Add(sgEntry, 1, wxEXPAND | wxALL, space5);
// OS X UX: ID_NO becomes "Don't Save" when paired with wxID_SAVE
wxStdDialogButtonSizer* buttons = new wxStdDialogButtonSizer();
buttons->AddButton(new wxButton(this, wxID_SAVE));
buttons->AddButton(new wxButton(this, wxID_NO, wxGetStockLabel(wxID_CANCEL)));
buttons->Realize();
sEditCheat->AddSpacer(space5);
sEditCheat->Add(sbEntry, 1, wxEXPAND | wxLEFT | wxRIGHT, space5);
sEditCheat->AddSpacer(space10);
sEditCheat->Add(buttons, 0, wxEXPAND | wxLEFT | wxRIGHT, space5);
sEditCheat->AddSpacer(space5);
Bind(wxEVT_BUTTON, &ARCodeAddEdit::SaveCheatData, this, wxID_SAVE);
SetEscapeId(wxID_NO);
SetAffirmativeId(wxID_SAVE);
SetSizerAndFit(sEditCheat);
}
void ARCodeAddEdit::SaveCheatData(wxCommandEvent& WXUNUSED(event))
{
std::vector<ActionReplay::AREntry> decrypted_lines;
std::vector<std::string> encrypted_lines;
// Split the entered cheat into lines.
std::vector<std::string> input_lines;
SplitString(WxStrToStr(m_cheat_codes->GetValue()), '\n', input_lines);
for (size_t i = 0; i < input_lines.size(); i++)
{
// Make sure to ignore unneeded whitespace characters.
std::string line_str = StripSpaces(input_lines[i]);
if (line_str.empty())
continue;
// Let's parse the current line. Is it in encrypted or decrypted form?
std::vector<std::string> pieces;
SplitString(line_str, ' ', pieces);
if (pieces.size() == 2 && pieces[0].size() == 8 && pieces[1].size() == 8)
{
// Decrypted code line.
u32 addr = std::stoul(pieces[0], nullptr, 16);
u32 value = std::stoul(pieces[1], nullptr, 16);
decrypted_lines.emplace_back(addr, value);
continue;
}
else if (pieces.size() == 1)
{
SplitString(line_str, '-', pieces);
if (pieces.size() == 3 && pieces[0].size() == 4 && pieces[1].size() == 4 &&
pieces[2].size() == 5)
{
// Encrypted code line. We'll have to decode it later.
encrypted_lines.emplace_back(pieces[0] + pieces[1] + pieces[2]);
continue;
}
}
// If the above-mentioned conditions weren't met, then something went wrong.
if (wxMessageBox(
wxString::Format(_("Unable to parse line %u of the entered AR code as a valid "
"encrypted or decrypted code. Make sure you typed it correctly.\n\n"
"Would you like to ignore this line and continue parsing?"),
(unsigned)(i + 1)),
_("Parsing Error"), wxYES_NO | wxICON_ERROR, this) == wxNO)
{
return;
}
}
// If the entered code was in encrypted form, we decode it here.
if (!encrypted_lines.empty())
{
// If the code contains a mixture of encrypted and unencrypted lines then we can't process it.
int mode = wxYES;
if (!decrypted_lines.empty())
{
mode =
wxMessageBox(_("This Action Replay code contains both encrypted and unencrypted lines; "
"you should check that you have entered it correctly.\n\n"
"Do you want to discard all unencrypted lines?"),
_("Invalid Mixed Code"), wxYES_NO | wxCANCEL | wxICON_ERROR, this);
// YES = Discard the unencrypted lines then decrypt the encrypted ones instead.
// NO = Discard the encrypted lines, keep the unencrypted ones
// CANCEL = Stop and let the user go back to editing
if (mode == wxCANCEL)
return;
if (mode == wxYES)
decrypted_lines.clear();
}
if (mode == wxYES)
ActionReplay::DecryptARCode(encrypted_lines, &decrypted_lines);
}
// There's no point creating a code with no content.
if (decrypted_lines.empty())
{
WxUtils::ShowErrorDialog(_("The resulting decrypted AR code doesn't contain any lines."));
return;
}
ActionReplay::ARCode new_code;
new_code.name = WxStrToStr(m_txt_cheat_name->GetValue());
new_code.ops = std::move(decrypted_lines);
new_code.active = true;
new_code.user_defined = true;
if (new_code.name != m_code.name || new_code.ops != m_code.ops)
{
m_code = std::move(new_code);
AcceptAndClose();
}
else
{
EndDialog(GetEscapeId());
}
}

View File

@ -0,0 +1,29 @@
// Copyright 2008 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#pragma once
#include <wx/dialog.h>
#include "Core/ActionReplay.h"
class wxTextCtrl;
class ARCodeAddEdit final : public wxDialog
{
public:
ARCodeAddEdit(ActionReplay::ARCode code, wxWindow* parent, wxWindowID id = wxID_ANY,
const wxString& title = _("Edit ActionReplay Code"),
const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize,
long style = wxDEFAULT_DIALOG_STYLE);
const ActionReplay::ARCode& GetCode() const { return m_code; }
private:
void CreateGUI();
void SaveCheatData(wxCommandEvent& event);
ActionReplay::ARCode m_code;
wxTextCtrl* m_txt_cheat_name;
wxTextCtrl* m_cheat_codes;
};

View File

@ -0,0 +1,291 @@
// Copyright 2016 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include <wx/button.h>
#include <wx/checklst.h>
#include <wx/font.h>
#include <wx/listbox.h>
#include <wx/sizer.h>
#include <wx/statbox.h>
#include <wx/stattext.h>
#include "DolphinWX/Cheats/ARCodeAddEdit.h"
#include "DolphinWX/Cheats/ActionReplayCodesPanel.h"
#include "DolphinWX/WxUtils.h"
wxDEFINE_EVENT(DOLPHIN_EVT_ARCODE_TOGGLED, wxCommandEvent);
ActionReplayCodesPanel::ActionReplayCodesPanel(wxWindow* parent, Style styles) : wxPanel(parent)
{
SetExtraStyle(GetExtraStyle() | wxWS_EX_BLOCK_EVENTS);
CreateGUI();
SetCodePanelStyle(styles);
}
ActionReplayCodesPanel::~ActionReplayCodesPanel()
{
}
void ActionReplayCodesPanel::LoadCodes(const IniFile& global_ini, const IniFile& local_ini)
{
m_codes = ActionReplay::LoadCodes(global_ini, local_ini);
m_was_modified = false;
Repopulate();
}
void ActionReplayCodesPanel::SaveCodes(IniFile* local_ini)
{
ActionReplay::SaveCodes(local_ini, m_codes);
m_was_modified = false;
}
void ActionReplayCodesPanel::AppendNewCode(const ActionReplay::ARCode& code)
{
m_codes.push_back(code);
int idx = m_checklist_cheats->Append(m_checklist_cheats->EscapeMnemonics(StrToWxStr(code.name)));
if (code.active)
m_checklist_cheats->Check(idx);
m_was_modified = true;
GenerateToggleEvent(code);
}
void ActionReplayCodesPanel::Clear()
{
m_was_modified = false;
m_codes.clear();
m_codes.shrink_to_fit();
Repopulate();
}
void ActionReplayCodesPanel::SetCodePanelStyle(Style styles)
{
m_styles = styles;
m_side_panel->GetStaticBox()->Show(!!(styles & STYLE_SIDE_PANEL));
m_modify_buttons->Show(!!(styles & STYLE_MODIFY_BUTTONS));
UpdateSidePanel();
UpdateModifyButtons();
Layout();
}
void ActionReplayCodesPanel::CreateGUI()
{
// STYLE_LIST
m_checklist_cheats = new wxCheckListBox(this, wxID_ANY);
// STYLE_SIDE_PANEL
m_side_panel = new wxStaticBoxSizer(wxVERTICAL, this, _("Code Info"));
m_label_code_name = new wxStaticText(m_side_panel->GetStaticBox(), wxID_ANY, _("Name: "),
wxDefaultPosition, wxDefaultSize, wxST_NO_AUTORESIZE);
m_label_num_codes =
new wxStaticText(m_side_panel->GetStaticBox(), wxID_ANY, _("Number of Codes: ") + '0');
m_list_codes = new wxListBox(m_side_panel->GetStaticBox(), wxID_ANY);
{
wxFont monospace{m_list_codes->GetFont()};
monospace.SetFamily(wxFONTFAMILY_TELETYPE);
#ifdef _WIN32
monospace.SetFaceName("Consolas"); // Windows always uses Courier New
#endif
m_list_codes->SetFont(monospace);
}
const int space5 = FromDIP(5);
m_side_panel->AddSpacer(space5);
m_side_panel->Add(m_label_code_name, 0, wxEXPAND | wxLEFT | wxRIGHT, space5);
m_side_panel->AddSpacer(space5);
m_side_panel->Add(m_label_num_codes, 0, wxEXPAND | wxLEFT | wxRIGHT, space5);
m_side_panel->AddSpacer(space5);
m_side_panel->Add(m_list_codes, 1, wxEXPAND | wxLEFT | wxRIGHT, space5);
m_side_panel->AddSpacer(space5);
m_side_panel->SetMinSize(FromDIP(wxSize(180, -1)));
// STYLE_MODIFY_BUTTONS
m_modify_buttons = new wxPanel(this);
wxButton* btn_add_code = new wxButton(m_modify_buttons, wxID_ANY, _("&Add New Code..."));
m_btn_edit_code = new wxButton(m_modify_buttons, wxID_ANY, _("&Edit Code..."));
m_btn_remove_code = new wxButton(m_modify_buttons, wxID_ANY, _("&Remove Code"));
wxBoxSizer* button_layout = new wxBoxSizer(wxHORIZONTAL);
button_layout->Add(btn_add_code);
button_layout->AddStretchSpacer();
button_layout->Add(m_btn_edit_code);
button_layout->Add(m_btn_remove_code);
m_modify_buttons->SetSizer(button_layout);
// Top level layouts
wxBoxSizer* panel_layout = new wxBoxSizer(wxHORIZONTAL);
panel_layout->Add(m_checklist_cheats, 1, wxEXPAND);
panel_layout->Add(m_side_panel, 0, wxEXPAND | wxLEFT, space5);
wxBoxSizer* main_layout = new wxBoxSizer(wxVERTICAL);
main_layout->Add(panel_layout, 1, wxEXPAND);
main_layout->Add(m_modify_buttons, 0, wxEXPAND | wxTOP, space5);
m_checklist_cheats->Bind(wxEVT_LISTBOX, &ActionReplayCodesPanel::OnCodeSelectionChanged, this);
m_checklist_cheats->Bind(wxEVT_LISTBOX_DCLICK, &ActionReplayCodesPanel::OnCodeDoubleClick, this);
m_checklist_cheats->Bind(wxEVT_CHECKLISTBOX, &ActionReplayCodesPanel::OnCodeChecked, this);
btn_add_code->Bind(wxEVT_BUTTON, &ActionReplayCodesPanel::OnAddNewCodeClick, this);
m_btn_edit_code->Bind(wxEVT_BUTTON, &ActionReplayCodesPanel::OnEditCodeClick, this);
m_btn_remove_code->Bind(wxEVT_BUTTON, &ActionReplayCodesPanel::OnRemoveCodeClick, this);
SetSizer(main_layout);
}
void ActionReplayCodesPanel::Repopulate()
{
// If the code editor is open then it's invalidated now.
// (whatever it was doing is no longer relevant)
if (m_editor)
m_editor->EndModal(wxID_NO);
m_checklist_cheats->Freeze();
m_checklist_cheats->Clear();
for (const auto& code : m_codes)
{
int idx =
m_checklist_cheats->Append(m_checklist_cheats->EscapeMnemonics(StrToWxStr(code.name)));
if (code.active)
m_checklist_cheats->Check(idx);
}
m_checklist_cheats->Thaw();
// Clear side panel contents since selection is invalidated
UpdateSidePanel();
UpdateModifyButtons();
}
void ActionReplayCodesPanel::UpdateSidePanel()
{
if (!(m_styles & STYLE_SIDE_PANEL))
return;
wxString name;
std::size_t code_count = 0;
if (m_checklist_cheats->GetSelection() != wxNOT_FOUND)
{
auto& code = m_codes.at(m_checklist_cheats->GetSelection());
name = StrToWxStr(code.name);
code_count = code.ops.size();
m_list_codes->Freeze();
m_list_codes->Clear();
for (const auto& entry : code.ops)
{
m_list_codes->Append(wxString::Format("%08X %08X", entry.cmd_addr, entry.value));
}
m_list_codes->Thaw();
}
else
{
m_list_codes->Clear();
}
m_label_code_name->SetLabelText(_("Name: ") + name);
m_label_code_name->Wrap(m_label_code_name->GetSize().GetWidth());
m_label_code_name->InvalidateBestSize();
m_label_num_codes->SetLabelText(wxString::Format("%s%zu", _("Number of Codes: "), code_count));
Layout();
}
void ActionReplayCodesPanel::UpdateModifyButtons()
{
if (!(m_styles & STYLE_MODIFY_BUTTONS))
return;
bool is_user_defined = true;
bool enable_buttons = false;
if (m_checklist_cheats->GetSelection() != wxNOT_FOUND)
{
is_user_defined = m_codes.at(m_checklist_cheats->GetSelection()).user_defined;
enable_buttons = true;
}
m_btn_edit_code->SetLabel(is_user_defined ? _("&Edit Code...") : _("Clone and &Edit Code..."));
m_btn_edit_code->Enable(enable_buttons);
m_btn_remove_code->Enable(enable_buttons && is_user_defined);
Layout();
}
void ActionReplayCodesPanel::GenerateToggleEvent(const ActionReplay::ARCode& code)
{
wxCommandEvent toggle_event{DOLPHIN_EVT_ARCODE_TOGGLED, GetId()};
toggle_event.SetClientData(const_cast<ActionReplay::ARCode*>(&code));
if (!GetEventHandler()->ProcessEvent(toggle_event))
{
// Because wxWS_EX_BLOCK_EVENTS affects all events, propagation needs to be done manually.
GetParent()->GetEventHandler()->ProcessEvent(toggle_event);
}
}
void ActionReplayCodesPanel::OnCodeChecked(wxCommandEvent& ev)
{
auto& code = m_codes.at(ev.GetSelection());
code.active = m_checklist_cheats->IsChecked(ev.GetSelection());
m_was_modified = true;
GenerateToggleEvent(code);
}
void ActionReplayCodesPanel::OnCodeSelectionChanged(wxCommandEvent&)
{
UpdateSidePanel();
UpdateModifyButtons();
}
void ActionReplayCodesPanel::OnCodeDoubleClick(wxCommandEvent& ev)
{
if (!(m_styles & STYLE_MODIFY_BUTTONS))
return;
OnEditCodeClick(ev);
}
void ActionReplayCodesPanel::OnAddNewCodeClick(wxCommandEvent&)
{
ARCodeAddEdit editor{{}, this, wxID_ANY, _("Add ActionReplay Code")};
m_editor = &editor;
if (editor.ShowModal() == wxID_SAVE)
AppendNewCode(editor.GetCode());
m_editor = nullptr;
}
void ActionReplayCodesPanel::OnEditCodeClick(wxCommandEvent&)
{
int idx = m_checklist_cheats->GetSelection();
wxASSERT(idx != wxNOT_FOUND);
auto& code = m_codes.at(idx);
// If the code is from the global INI then we'll have to clone it.
if (!code.user_defined)
{
ARCodeAddEdit editor{code, this, wxID_ANY, _("Duplicate Bundled ActionReplay Code")};
m_editor = &editor;
if (editor.ShowModal() == wxID_SAVE)
AppendNewCode(editor.GetCode());
m_editor = nullptr;
return;
}
ARCodeAddEdit editor{code, this};
m_editor = &editor;
if (editor.ShowModal() == wxID_SAVE)
{
code = editor.GetCode();
m_checklist_cheats->SetString(idx, m_checklist_cheats->EscapeMnemonics(StrToWxStr(code.name)));
m_checklist_cheats->Check(idx, code.active);
m_was_modified = true;
UpdateSidePanel();
GenerateToggleEvent(code);
}
m_editor = nullptr;
}
void ActionReplayCodesPanel::OnRemoveCodeClick(wxCommandEvent&)
{
int idx = m_checklist_cheats->GetSelection();
wxASSERT(idx != wxNOT_FOUND);
m_codes.erase(m_codes.begin() + idx);
m_checklist_cheats->Delete(idx);
m_was_modified = true;
}

View File

@ -0,0 +1,89 @@
// Copyright 2016 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#pragma once
#include <string>
#include <vector>
#include <wx/panel.h>
#include "Core/ActionReplay.h"
class wxButton;
class wxCheckListBox;
class wxListBox;
class wxStaticBoxSizer;
class wxStaticText;
class ARCodeAddEdit;
class IniFile;
// GetClientData() -> ActionReplay::ARCode* [immutable]
wxDECLARE_EVENT(DOLPHIN_EVT_ARCODE_TOGGLED, wxCommandEvent);
class ActionReplayCodesPanel final : public wxPanel
{
public:
enum Style
{
STYLE_LIST = 0, // Show checklist
STYLE_SIDE_PANEL = 1, // Show side panel displaying code content
STYLE_MODIFY_BUTTONS = 2 // Show buttons for adding/editing/removing codes
};
explicit ActionReplayCodesPanel(wxWindow* parent, Style styles = STYLE_LIST);
~ActionReplayCodesPanel() override;
void LoadCodes(const IniFile& global_ini, const IniFile& local_ini);
const std::vector<ActionReplay::ARCode>& GetCodes() { return m_codes; }
void SaveCodes(IniFile* local_ini);
void AppendNewCode(const ActionReplay::ARCode& code);
void Clear();
bool IsModified() const { return m_was_modified; }
void ClearModified() { m_was_modified = false; }
Style GetCodePanelStyle() const { return m_styles; }
void SetCodePanelStyle(Style style);
private:
void CreateGUI();
void Repopulate();
void UpdateSidePanel();
void UpdateModifyButtons();
void GenerateToggleEvent(const ActionReplay::ARCode& code);
void OnCodeSelectionChanged(wxCommandEvent&);
void OnCodeChecked(wxCommandEvent&);
void OnCodeDoubleClick(wxCommandEvent&);
void OnAddNewCodeClick(wxCommandEvent&);
void OnEditCodeClick(wxCommandEvent&);
void OnRemoveCodeClick(wxCommandEvent&);
std::vector<ActionReplay::ARCode> m_codes;
wxStaticText* m_label_code_name = nullptr;
wxStaticText* m_label_num_codes = nullptr;
wxCheckListBox* m_checklist_cheats = nullptr;
wxListBox* m_list_codes = nullptr;
wxPanel* m_modify_buttons = nullptr;
wxButton* m_btn_edit_code = nullptr;
wxButton* m_btn_remove_code = nullptr;
wxStaticBoxSizer* m_side_panel = nullptr;
ARCodeAddEdit* m_editor = nullptr;
Style m_styles = STYLE_LIST;
bool m_was_modified = false;
};
constexpr ActionReplayCodesPanel::Style operator|(ActionReplayCodesPanel::Style a,
ActionReplayCodesPanel::Style b)
{
return static_cast<ActionReplayCodesPanel::Style>(static_cast<int>(a) | b);
}
constexpr ActionReplayCodesPanel::Style operator&(ActionReplayCodesPanel::Style a,
ActionReplayCodesPanel::Style b)
{
return static_cast<ActionReplayCodesPanel::Style>(static_cast<int>(a) & b);
}

View File

@ -32,6 +32,7 @@
#include "Core/Core.h"
#include "Core/GeckoCode.h"
#include "Core/GeckoCodeConfig.h"
#include "DolphinWX/Cheats/ActionReplayCodesPanel.h"
#include "DolphinWX/Cheats/CheatSearchTab.h"
#include "DolphinWX/Cheats/CheatsWindow.h"
#include "DolphinWX/Cheats/CreateCodeDialog.h"
@ -58,7 +59,7 @@ wxCheatsWindow::wxCheatsWindow(wxWindow* const parent)
// load codes
UpdateGUI();
wxTheApp->Bind(DOLPHIN_EVT_LOCAL_INI_CHANGED, &wxCheatsWindow::OnEvent_CheatsList_Update, this);
wxTheApp->Bind(DOLPHIN_EVT_LOCAL_INI_CHANGED, &wxCheatsWindow::OnLocalGameIniModified, this);
SetSize(wxSize(-1, 600));
Center();
@ -77,31 +78,16 @@ void wxCheatsWindow::Init_ChildControls()
// --- Tabs ---
// Cheats List Tab
m_tab_cheats = new wxPanel(m_notebook_main, wxID_ANY);
wxPanel* tab_cheats = new wxPanel(m_notebook_main, wxID_ANY);
m_checklistbox_cheats_list = new wxCheckListBox(m_tab_cheats, wxID_ANY, wxDefaultPosition,
wxSize(300, 0), 0, nullptr, wxLB_HSCROLL);
m_checklistbox_cheats_list->Bind(wxEVT_LISTBOX, &wxCheatsWindow::OnEvent_CheatsList_ItemSelected,
this);
m_label_code_name = new wxStaticText(m_tab_cheats, wxID_ANY, _("Name: "), wxDefaultPosition,
wxDefaultSize, wxST_NO_AUTORESIZE);
m_groupbox_info = new wxStaticBox(m_tab_cheats, wxID_ANY, _("Code Info"));
m_label_num_codes = new wxStaticText(m_tab_cheats, wxID_ANY, _("Number Of Codes: "));
m_listbox_codes_list = new wxListBox(m_tab_cheats, wxID_ANY, wxDefaultPosition, wxSize(120, 150),
0, nullptr, wxLB_HSCROLL);
wxStaticBoxSizer* sGroupBoxInfo = new wxStaticBoxSizer(m_groupbox_info, wxVERTICAL);
sGroupBoxInfo->Add(m_label_code_name, 0, wxEXPAND | wxALL, 5);
sGroupBoxInfo->Add(m_label_num_codes, 0, wxALL, 5);
sGroupBoxInfo->Add(m_listbox_codes_list, 1, wxALL, 5);
m_ar_codes_panel =
new ActionReplayCodesPanel(tab_cheats, ActionReplayCodesPanel::STYLE_SIDE_PANEL |
ActionReplayCodesPanel::STYLE_MODIFY_BUTTONS);
wxBoxSizer* sizer_tab_cheats = new wxBoxSizer(wxHORIZONTAL);
sizer_tab_cheats->Add(m_checklistbox_cheats_list, 1, wxEXPAND | wxTOP | wxBOTTOM | wxLEFT, 10);
sizer_tab_cheats->Add(sGroupBoxInfo, 0, wxALIGN_LEFT | wxEXPAND | wxALL, 5);
sizer_tab_cheats->Add(m_ar_codes_panel, 1, wxEXPAND | wxALL, 5);
m_tab_cheats->SetSizerAndFit(sizer_tab_cheats);
tab_cheats->SetSizerAndFit(sizer_tab_cheats);
// Cheat Search Tab
wxPanel* const tab_cheat_search = new CheatSearchTab(m_notebook_main);
@ -134,7 +120,7 @@ void wxCheatsWindow::Init_ChildControls()
m_tab_log->SetSizerAndFit(sTabLog);
// Add Tabs to Notebook
m_notebook_main->AddPage(m_tab_cheats, _("AR Codes"));
m_notebook_main->AddPage(tab_cheats, _("AR Codes"));
m_geckocode_panel = new Gecko::CodeConfigPanel(m_notebook_main);
m_notebook_main->AddPage(m_geckocode_panel, _("Gecko Codes"));
m_notebook_main->AddPage(tab_cheat_search, _("Cheat Search"));
@ -193,21 +179,17 @@ void wxCheatsWindow::UpdateGUI()
void wxCheatsWindow::Load_ARCodes()
{
m_checklistbox_cheats_list->Clear();
if (!Core::IsRunning())
return;
m_checklistbox_cheats_list->Freeze();
for (auto& code : ActionReplay::LoadCodes(m_gameini_default, m_gameini_local))
{
CodeData* cd = new CodeData();
cd->code = std::move(code);
int index = m_checklistbox_cheats_list->Append(
wxCheckListBox::EscapeMnemonics(StrToWxStr(cd->code.name)), cd);
m_checklistbox_cheats_list->Check(index, cd->code.active);
m_ar_codes_panel->Clear();
m_ar_codes_panel->Disable();
return;
}
m_checklistbox_cheats_list->Thaw();
else if (!m_ar_codes_panel->IsEnabled())
{
m_ar_codes_panel->Enable();
}
m_ar_codes_panel->LoadCodes(m_gameini_default, m_gameini_local);
}
void wxCheatsWindow::Load_GeckoCodes()
@ -220,36 +202,10 @@ void wxCheatsWindow::OnNewARCodeCreated(wxCommandEvent& ev)
{
auto code = static_cast<ActionReplay::ARCode*>(ev.GetClientData());
ActionReplay::AddCode(*code);
CodeData* cd = new CodeData();
cd->code = *code;
int idx = m_checklistbox_cheats_list->Append(
wxCheckListBox::EscapeMnemonics(StrToWxStr(code->name)), cd);
m_checklistbox_cheats_list->Check(idx, code->active);
m_ar_codes_panel->AppendNewCode(*code);
}
void wxCheatsWindow::OnEvent_CheatsList_ItemSelected(wxCommandEvent& event)
{
CodeData* cd = static_cast<CodeData*>(event.GetClientObject());
m_label_code_name->SetLabelText(_("Name: ") + StrToWxStr(cd->code.name));
m_label_code_name->Wrap(m_label_code_name->GetSize().GetWidth());
m_label_code_name->InvalidateBestSize();
m_label_num_codes->SetLabelText(
wxString::Format("%s%zu", _("Number Of Codes: "), cd->code.ops.size()));
m_listbox_codes_list->Freeze();
m_listbox_codes_list->Clear();
for (const ActionReplay::AREntry& entry : cd->code.ops)
{
m_listbox_codes_list->Append(wxString::Format("%08x %08x", entry.cmd_addr, entry.value));
}
m_listbox_codes_list->Thaw();
m_tab_cheats->Layout();
}
void wxCheatsWindow::OnEvent_CheatsList_Update(wxCommandEvent& ev)
void wxCheatsWindow::OnLocalGameIniModified(wxCommandEvent& ev)
{
ev.Skip();
if (WxStrToStr(ev.GetString()) != m_game_id)
@ -264,18 +220,8 @@ void wxCheatsWindow::OnEvent_CheatsList_Update(wxCommandEvent& ev)
void wxCheatsWindow::OnEvent_ApplyChanges_Press(wxCommandEvent& ev)
{
// Convert embedded metadata back into ARCode vector and update active states
std::vector<ActionReplay::ARCode> code_vec;
code_vec.reserve(m_checklistbox_cheats_list->GetCount());
for (unsigned int i = 0; i < m_checklistbox_cheats_list->GetCount(); ++i)
{
CodeData* cd = static_cast<CodeData*>(m_checklistbox_cheats_list->GetClientObject(i));
cd->code.active = m_checklistbox_cheats_list->IsChecked(i);
code_vec.push_back(cd->code);
}
// Apply Action Replay code changes
ActionReplay::ApplyCodes(code_vec);
ActionReplay::ApplyCodes(m_ar_codes_panel->GetCodes());
// Apply Gecko Code changes
Gecko::SetActiveCodes(m_geckocode_panel->GetCodes());
@ -283,7 +229,7 @@ void wxCheatsWindow::OnEvent_ApplyChanges_Press(wxCommandEvent& ev)
// Save gameini, with changed codes
if (m_gameini_local_path.size())
{
ActionReplay::SaveCodes(&m_gameini_local, code_vec);
m_ar_codes_panel->SaveCodes(&m_gameini_local);
Gecko::SaveCodes(m_gameini_local, m_geckocode_panel->GetCodes());
m_gameini_local.Save(m_gameini_local_path);
@ -302,9 +248,31 @@ void wxCheatsWindow::OnEvent_ButtonUpdateLog_Press(wxCommandEvent& WXUNUSED(even
wxBeginBusyCursor();
m_textctrl_log->Freeze();
m_textctrl_log->Clear();
// This horrible mess is because the Windows Textbox Widget suffers from
// a Shlemiel The Painter problem where it keeps allocating new memory each
// time some text is appended then memcpys to the new buffer. This happens
// for every single line resulting in the operation taking minutes instead of
// seconds.
// Why not just append all of the text all at once? Microsoft decided that it
// would be clever to accept as much text as will fit in the internal buffer
// then silently discard the rest. We have to iteratively append the text over
// and over until the internal buffer becomes big enough to hold all of it.
// (wxWidgets should have hidden this platform detail but it sucks)
wxString super_string;
super_string.reserve(1024 * 1024);
for (const std::string& text : ActionReplay::GetSelfLog())
{
m_textctrl_log->AppendText(StrToWxStr(text));
super_string.append(StrToWxStr(text));
}
while (!super_string.empty())
{
// Read "GetLastPosition" as "Size", there's no size function.
wxTextPos start = m_textctrl_log->GetLastPosition();
m_textctrl_log->AppendText(super_string);
wxTextPos end = m_textctrl_log->GetLastPosition();
if (start == end)
break;
super_string.erase(0, end - start);
}
m_textctrl_log->Thaw();
wxEndBusyCursor();

View File

@ -16,18 +16,14 @@
class wxButton;
class wxCheckBox;
class wxCheckListBox;
class wxCloseEvent;
class wxListBox;
class wxNotebook;
class wxStaticBox;
class wxStaticText;
class wxTextCtrl;
namespace Gecko
{
class CodeConfigPanel;
}
class ActionReplayCodesPanel;
wxDECLARE_EVENT(DOLPHIN_EVT_ADD_NEW_ACTION_REPLAY_CODE, wxCommandEvent);
@ -45,22 +41,13 @@ private:
wxButton* m_button_apply;
wxNotebook* m_notebook_main;
wxPanel* m_tab_cheats;
wxPanel* m_tab_log;
wxCheckBox* m_checkbox_log_ar;
wxStaticText* m_label_code_name;
wxStaticText* m_label_num_codes;
wxCheckListBox* m_checklistbox_cheats_list;
wxTextCtrl* m_textctrl_log;
wxListBox* m_listbox_codes_list;
wxStaticBox* m_groupbox_info;
ActionReplayCodesPanel* m_ar_codes_panel;
Gecko::CodeConfigPanel* m_geckocode_panel;
IniFile m_gameini_default;
IniFile m_gameini_local;
@ -83,9 +70,8 @@ private:
void OnEvent_ButtonClose_Press(wxCommandEvent& event);
void OnEvent_Close(wxCloseEvent& ev);
// Cheats List
void OnEvent_CheatsList_ItemSelected(wxCommandEvent& event);
void OnEvent_CheatsList_Update(wxCommandEvent& event);
// Changes to the INI (affects cheat listings)
void OnLocalGameIniModified(wxCommandEvent& event);
// Apply Changes Button
void OnEvent_ApplyChanges_Press(wxCommandEvent& event);