snes9x/gtk/src/gtk_cheat.cpp
2023-03-07 13:58:21 -06:00

381 lines
11 KiB
C++

/*****************************************************************************\
Snes9x - Portable Super Nintendo Entertainment System (TM) emulator.
This file is licensed under the Snes9x License.
For further information, consult the LICENSE file in the root directory.
\*****************************************************************************/
#include "gtk_s9x.h"
#include "gtk_cheat.h"
#include "cheats.h"
#include "display.h"
enum {
COLUMN_ENABLED = 0,
COLUMN_DESCRIPTION = 1,
COLUMN_CHEAT = 2,
NUM_COLS
};
extern SCheatData Cheat;
static void display_errorbox(const char *error)
{
auto dialog = Gtk::MessageDialog(error, false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
dialog.set_title(_("Error"));
dialog.run();
dialog.hide();
}
static Snes9xCheats *cheats_dialog = nullptr;
void open_snes9x_cheats_dialog()
{
if (!cheats_dialog)
cheats_dialog = new Snes9xCheats;
cheats_dialog->show();
}
Snes9xCheats::Snes9xCheats()
: GtkBuilderWindow("cheat_window")
{
dst_row = -1;
auto view = get_object<Gtk::TreeView>("cheat_treeview");
view->signal_row_activated().connect(sigc::mem_fun(*this, &Snes9xCheats::row_activated));
view->set_reorderable();
auto toggle_renderer = new Gtk::CellRendererToggle();
toggle_renderer->set_activatable();
toggle_renderer->signal_toggled().connect(sigc::mem_fun(*this, &Snes9xCheats::toggle_code));
view->insert_column("\xe2\x86\x91", *toggle_renderer, COLUMN_ENABLED);
auto column = view->get_column(COLUMN_ENABLED);
column->set_clickable();
column->set_alignment(0.5);
column->add_attribute(*toggle_renderer, "active", COLUMN_ENABLED);
column->signal_clicked().connect([&] {
sort_cheats();
});
auto text_renderer = new Gtk::CellRendererText();
view->insert_column(_("Description"), *text_renderer, COLUMN_DESCRIPTION);
column = view->get_column(COLUMN_DESCRIPTION);
column->set_resizable();
column->set_min_width(40);
column->add_attribute(*text_renderer, "text", COLUMN_DESCRIPTION);
view->insert_column(_("Cheat"), *text_renderer, COLUMN_CHEAT);
column = view->get_column(COLUMN_CHEAT);
column->set_resizable();
column->set_min_width(40);
column->add_attribute(*text_renderer, "text", COLUMN_CHEAT);
Gtk::TreeModelColumn<bool> column1;
Gtk::TreeModelColumn<Glib::ustring> column2;
Gtk::TreeModelColumn<Glib::ustring> column3;
Gtk::TreeModelColumnRecord record;
record.add(column1);
record.add(column2);
record.add(column3);
store = Gtk::ListStore::create(record);
view->set_model(store);
delete_id = store->signal_row_deleted().connect([&](const Gtk::TreeModel::Path &path) {
row_deleted(get_index_from_path(path));
});
insert_id = store->signal_row_inserted().connect([&](const Gtk::TreeModel::Path &path, const Gtk::TreeModel::iterator &iter) {
row_inserted(get_index_from_path(path));
});
get_object<Gtk::Button>("add_code")->signal_clicked().connect(sigc::mem_fun(*this, &Snes9xCheats::add_code));
get_object<Gtk::Button>("remove_code")->signal_clicked().connect(sigc::mem_fun(*this, &Snes9xCheats::remove_code));
get_object<Gtk::Button>("update_button")->signal_clicked().connect(sigc::mem_fun(*this, &Snes9xCheats::update_code));
get_object<Gtk::Button>("disable_all_button")->signal_clicked().connect(sigc::mem_fun(*this, &Snes9xCheats::disable_all));
get_object<Gtk::Button>("delete_all_cheats_button")->signal_clicked().connect(sigc::mem_fun(*this, &Snes9xCheats::delete_all_cheats));
get_object<Gtk::Button>("cheat_search_button")->signal_clicked().connect(sigc::mem_fun(*this, &Snes9xCheats::search_database));
get_object<Gtk::Button>("update_button")->signal_clicked().connect(sigc::mem_fun(*this, &Snes9xCheats::update_code));
gtk_widget_realize(GTK_WIDGET(window->gobj()));
}
Snes9xCheats::~Snes9xCheats()
{
}
void Snes9xCheats::enable_dnd(bool enable)
{
if (enable)
{
delete_id.unblock();
insert_id.unblock();
}
else
{
delete_id.block();
insert_id.unblock();
}
}
void Snes9xCheats::show()
{
top_level->pause_from_focus_change();
window->set_transient_for(*top_level->window.get());
refresh_tree_view();
Glib::RefPtr<Gtk::Dialog>::cast_static(window)->run();
window->hide();
top_level->unpause_from_focus_change();
}
static void cheat_move(int src, int dst)
{
Cheat.group.insert(Cheat.group.begin() + dst, Cheat.group[src]);
if (dst < src)
src++;
Cheat.group.erase(Cheat.group.begin() + src);
}
static void cheat_gather_enabled()
{
unsigned int enabled = 0;
for (unsigned int i = 0; i < Cheat.group.size(); i++)
{
if (Cheat.group[i].enabled && i >= enabled)
{
cheat_move(i, enabled);
enabled++;
}
}
}
void Snes9xCheats::row_deleted(int src_row)
{
if (dst_row >= 0)
{
if (src_row >= dst_row)
src_row--;
cheat_move(src_row, dst_row);
dst_row = -1;
}
}
void Snes9xCheats::row_inserted(int new_row)
{
dst_row = new_row;
}
int Snes9xCheats::get_selected_index()
{
auto selection = get_object<Gtk::TreeView>("cheat_treeview")->get_selection();
auto rows = selection->get_selected_rows();
if (rows.empty())
return -1;
auto indices = gtk_tree_path_get_indices(rows[0].gobj());
return indices[0];
}
int Snes9xCheats::get_index_from_path(Gtk::TreeModel::Path path)
{
gint *indices = gtk_tree_path_get_indices(path.gobj());
int index = indices[0];
return index;
}
int Snes9xCheats::get_index_from_path(const Glib::ustring &path)
{
return get_index_from_path(Gtk::TreeModel::Path(path));
}
void Snes9xCheats::refresh_tree_view()
{
enable_dnd(false);
auto list_size = store->children().size();
if (Cheat.group.size() == 0)
return;
for (unsigned int i = 0; i < Cheat.group.size() - list_size; i++)
store->append();
auto iter = store->children().begin();
for (unsigned int i = 0; i < Cheat.group.size (); i++)
{
auto str = S9xCheatGroupToText(i);
Glib::ustring description = Cheat.group[i].name[0] == '\0' ? "" :Cheat.group[i].name;
iter->set_value(COLUMN_ENABLED, Cheat.group[i].enabled);
iter->set_value(COLUMN_DESCRIPTION, description);
iter->set_value(COLUMN_CHEAT, Glib::ustring(str));
iter++;
}
enable_dnd(true);
}
void Snes9xCheats::add_code()
{
std::string code = get_entry_text("code_entry");
std::string description = get_entry_text("description_entry");
if (description.empty())
description = _("No description");
if (S9xAddCheatGroup(description, code) < 0)
{
display_errorbox(_("Couldn't find any cheat codes in input."));
return;
}
auto parsed_code = S9xCheatGroupToText(Cheat.group.size() - 1);
set_entry_text("code_entry", parsed_code);
get_object<Gtk::Entry>("code_entry")->grab_focus();
refresh_tree_view();
while (Gtk::Main::events_pending())
Gtk::Main::iteration(false);
auto selection = get_object<Gtk::TreeView>("cheat_treeview")->get_selection();
Gtk::TreePath path;
path.push_back(Cheat.group.size() - 1);
selection->select(path);
auto adj = get_object<Gtk::ScrolledWindow>("cheat_scrolledwindow")->get_vadjustment();
adj->set_value(adj->get_upper());
}
void Snes9xCheats::remove_code()
{
int index = get_selected_index();
if (index < 0)
return;
enable_dnd(false);
Gtk::TreePath path;
path.push_back(index);
auto iter = store->get_iter(path);
store->erase(iter);
enable_dnd(true);
S9xDeleteCheatGroup(index);
}
void Snes9xCheats::delete_all_cheats()
{
enable_dnd(false);
S9xDeleteCheats();
store->clear();
enable_dnd(true);
}
void Snes9xCheats::search_database()
{
std::string filename;
int result;
int reason = 0;
for (const auto &dir : { S9xGetDirectory(CHEAT_DIR),
get_config_dir(),
std::string(DATADIR) })
{
filename = dir + "/cheats.bml";
result = S9xImportCheatsFromDatabase(filename);
if (result == 0)
{
refresh_tree_view();
return;
}
if (result < reason)
reason = result;
}
auto dialog = Gtk::MessageDialog(*window.get(), reason == -1 ? _("Couldn't Find Cheats Database") : _("No Matching Game Found"), true);
dialog.set_secondary_text(reason == -1 ? _("The database file <b>cheats.bml</b> was not found. It is normally installed with "
"Snes9x, but you may also place a custom copy in your configuration or cheats directory.")
: _("No matching game was found in the databases. If you are using a non-official "
"translation or modified copy, you may be able to find and manually enter the codes."), true);
dialog.run();
dialog.hide();
}
void Snes9xCheats::sort_cheats()
{
cheat_gather_enabled();
refresh_tree_view();
}
void Snes9xCheats::row_activated(const Gtk::TreeModel::Path &path, Gtk::TreeViewColumn *column)
{
int index = get_index_from_path(path);
auto cheat_text = S9xCheatGroupToText(index);
set_entry_text("code_entry", cheat_text);
set_entry_text("description_entry", Cheat.group[index].name);
}
void Snes9xCheats::toggle_code(const Glib::ustring &path)
{
int index = get_index_from_path(path);
auto iter = store->get_iter(path);
bool enabled;
iter->get_value(COLUMN_ENABLED, enabled);
enabled = !enabled;
iter->set_value(COLUMN_ENABLED, enabled);
if (enabled)
S9xEnableCheatGroup(index);
else
S9xDisableCheatGroup(index);
}
void Snes9xCheats::update_code()
{
int index = get_selected_index();
if (index < 0)
return;
std::string code = get_entry_text("code_entry");
std::string description = get_entry_text("description_entry");
if (description.empty())
description = _("No description");
auto parsed_code = S9xCheatValidate(code);
if (parsed_code.empty())
{
display_errorbox(_("Couldn't find any cheat codes in input."));
return;
}
S9xModifyCheatGroup(index, description, parsed_code);
set_entry_text("code_entry", parsed_code);
get_object<Gtk::Entry>("code_entry")->grab_focus();
refresh_tree_view();
}
void Snes9xCheats::disable_all()
{
for (unsigned int i = 0; i < Cheat.group.size(); i++)
{
if (Cheat.group[i].enabled)
S9xDisableCheatGroup(i);
}
refresh_tree_view();
}