e17ff69533
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
412 lines
11 KiB
C++
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();
|
|
}
|