/*****************************************************************************\ 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("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 column1; Gtk::TreeModelColumn column2; Gtk::TreeModelColumn 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("add_code")->signal_clicked().connect(sigc::mem_fun(*this, &Snes9xCheats::add_code)); get_object("remove_code")->signal_clicked().connect(sigc::mem_fun(*this, &Snes9xCheats::remove_code)); get_object("update_button")->signal_clicked().connect(sigc::mem_fun(*this, &Snes9xCheats::update_code)); get_object("disable_all_button")->signal_clicked().connect(sigc::mem_fun(*this, &Snes9xCheats::disable_all)); get_object("delete_all_cheats_button")->signal_clicked().connect(sigc::mem_fun(*this, &Snes9xCheats::delete_all_cheats)); get_object("cheat_search_button")->signal_clicked().connect(sigc::mem_fun(*this, &Snes9xCheats::search_database)); get_object("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::cast_static(window)->run(); window->hide(); top_level->unpause_from_focus_change(); } static void cheat_move(int src, int dst) { Cheat.g.insert(Cheat.g.begin() + dst, Cheat.g[src]); if (dst < src) src++; Cheat.g.erase(Cheat.g.begin() + src); } static void cheat_gather_enabled() { unsigned int enabled = 0; for (unsigned int i = 0; i < Cheat.g.size(); i++) { if (Cheat.g[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("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.g.size() == 0) return; for (unsigned int i = 0; i < Cheat.g.size() - list_size; i++) store->append(); auto iter = store->children().begin(); for (unsigned int i = 0; i < Cheat.g.size (); i++) { char *str = S9xCheatGroupToText(i); Glib::ustring description = Cheat.g[i].name[0] == '\0' ? "" :Cheat.g[i].name; iter->set_value(COLUMN_ENABLED, Cheat.g[i].enabled); iter->set_value(COLUMN_DESCRIPTION, description); iter->set_value(COLUMN_CHEAT, Glib::ustring(str)); iter++; delete[] str; } 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.c_str(), code.c_str()) < 0) { display_errorbox(_("Couldn't find any cheat codes in input.")); return; } auto parsed_code = S9xCheatGroupToText(Cheat.g.size() - 1); set_entry_text("code_entry", parsed_code); delete[] parsed_code; get_object("code_entry")->grab_focus(); refresh_tree_view(); while (Gtk::Main::events_pending()) Gtk::Main::iteration(false); auto selection = get_object("cheat_treeview")->get_selection(); Gtk::TreePath path; path.push_back(Cheat.g.size() - 1); selection->select(path); auto adj = get_object("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; filename = S9xGetDirectory(CHEAT_DIR); filename += "/cheats.bml"; if (!(result = S9xImportCheatsFromDatabase(filename.c_str()))) { refresh_tree_view(); return; } if (result < reason) reason = result; filename = get_config_dir() + "/cheats.bml"; if (!(result = S9xImportCheatsFromDatabase(filename.c_str()))) { refresh_tree_view(); return; } if (result < reason) reason = result; filename = std::string(DATADIR) + "/cheats.bml"; if (!(result = S9xImportCheatsFromDatabase(filename.c_str()))) { refresh_tree_view(); return; } if (result < reason) reason = result; filename = S9xGetDirectory(ROM_DIR); filename += "/cheats.bml"; if (!(result = S9xImportCheatsFromDatabase(filename.c_str()))) { 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 cheats.bml 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.")); 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); char *cheat_text; cheat_text = S9xCheatGroupToText(index); set_entry_text("code_entry", cheat_text); delete[] cheat_text; set_entry_text("description_entry", Cheat.g[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.c_str()); if (!parsed_code) { display_errorbox(_("Couldn't find any cheat codes in input.")); return; } S9xModifyCheatGroup(index, description.c_str(), parsed_code); set_entry_text("code_entry", parsed_code); delete[] parsed_code; get_object("code_entry")->grab_focus(); refresh_tree_view(); } void Snes9xCheats::disable_all() { for (unsigned int i = 0; i < Cheat.g.size(); i++) { if (Cheat.g[i].enabled) S9xDisableCheatGroup(i); } refresh_tree_view(); }