snes9x/gtk/src/gtk_cheat.cpp
Brandon Wright e17ff69533 Gtk: Switch codebase to gtkmm.
GTK: Remove support for GTK+ 2.0.

GTK 3 is stable and widespread enough now.

GTK: Rearrange headers to eliminate gtk_s9xcore.h

Gtk: Initial gtkmm conversion work.

Gtk: More gtkmm conversion and bug fixing.

Gtk: More gtkmm fixes.

Gtk: More Fixes

OpenGL no longer creates a second window.
Accelerators are fixed.

Gtk: More fixes

Removed GLX context dependency on Gtk.

Gtk: Fix formatting.

Gtk: Remove a #pragma once
2020-07-17 14:48:34 -05:00

412 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.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<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.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<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.g.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;
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 <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."));
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<Gtk::Entry>("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();
}