diff --git a/desmume/src/cheatSystem.cpp b/desmume/src/cheatSystem.cpp index 03993b54d..4bdc76576 100755 --- a/desmume/src/cheatSystem.cpp +++ b/desmume/src/cheatSystem.cpp @@ -849,6 +849,16 @@ bool CHEATS::remove(const size_t pos) return didRemoveItem; } +void CHEATS::toggle(bool enabled, const size_t pos) +{ + this->_list[pos].enabled = (enabled) ? 1 : 0; +} + +void CHEATS::toggle(u8 enabled, const size_t pos) +{ + this->toggle((enabled != 0), pos); +} + void CHEATS::getListReset() { this->_currentGet = 0; diff --git a/desmume/src/cheatSystem.h b/desmume/src/cheatSystem.h index e4bda9f4c..b76c1084f 100755 --- a/desmume/src/cheatSystem.h +++ b/desmume/src/cheatSystem.h @@ -119,6 +119,9 @@ public: bool update_CB(char *code, char *description, u8 enabled, const size_t pos); bool remove(const size_t pos); + + void toggle(bool enabled, const size_t pos); + void toggle(u8 enablbed, const size_t pos); void getListReset(); bool getList(CHEATS_LIST *cheat); diff --git a/desmume/src/frontend/posix/gtk/cheatsGTK.cpp b/desmume/src/frontend/posix/gtk/cheatsGTK.cpp index 305564684..e33b024f8 100644 --- a/desmume/src/frontend/posix/gtk/cheatsGTK.cpp +++ b/desmume/src/frontend/posix/gtk/cheatsGTK.cpp @@ -24,6 +24,7 @@ #include #include "cheatsGTK.h" #include "cheatSystem.h" +#include "utilsGTK.h" #include "main.h" #include "desmume.h" @@ -98,10 +99,7 @@ enabled_toggled(GtkCellRendererToggle * cell, gtk_tree_model_get(store, &iter, COLUMN_INDEX, &ii, -1); - cheats->copyItemFromIndex(ii, tempCheatItem); - - cheats->update(tempCheatItem.size, tempCheatItem.code[0][0], tempCheatItem.code[0][1], tempCheatItem.description, - cheatEnabled, ii); + cheats->toggle(cheatEnabled, ii); gtk_list_store_set(GTK_LIST_STORE(store), &iter, COLUMN_ENABLED, guiEnabled, -1); @@ -159,10 +157,14 @@ static void cheat_list_modify_cheat(GtkCellRendererText * cell, } gtk_list_store_set(GTK_LIST_STORE(store), &iter, column, v, -1); } else if (column == COLUMN_DESC){ - cheats->update(cheat.size, cheat.code[0][0], cheat.code[0][1], - g_strdup(new_text), cheat.enabled, ii); + cheats->setDescription(g_strdup(new_text), ii); gtk_list_store_set(GTK_LIST_STORE(store), &iter, column, g_strdup(new_text), -1); + } else if (column == COLUMN_AR) { + bool isValid = cheats->update_AR(g_strdup(new_text), cheat.description, cheat.enabled, ii); + if (isValid) { + gtk_list_store_set(GTK_LIST_STORE(store), &iter, column, g_strdup(new_text), -1); + } } } @@ -225,7 +227,7 @@ static void cheat_list_add_cheat_AR(GtkWidget * widget, gpointer data) #define NEW_AR "00000000 00000000" GtkListStore *store = (GtkListStore *) data; GtkTreeIter iter; - cheats->add_AR("00000000 00000000", g_strdup(NEW_DESC), false); + cheats->add_AR(g_strdup(NEW_AR), g_strdup(NEW_DESC), false); gtk_list_store_append(store, &iter); gtk_list_store_set(store, &iter, COLUMN_INDEX, cheats->getListSize() - 1, @@ -282,7 +284,7 @@ static void cheat_list_address_to_hex(GtkTreeViewColumn * column, g_object_set(renderer, "text", hex_addr, NULL); } -static void cheat_list_add_columns(GtkTreeView * tree, GtkListStore * store) +static void cheat_list_add_columns(GtkTreeView * tree, GtkListStore * store, u8 cheat_type) { GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(tree)); @@ -300,7 +302,10 @@ static void cheat_list_add_columns(GtkTreeView * tree, GtkListStore * store) attrib = "active"; break; case TYPE_STRING: - renderer = gtk_cell_renderer_text_new(); + if (cheat_type == CHEAT_TYPE_INTERNAL) + renderer = gtk_cell_renderer_text_new(); + else if (cheat_type == CHEAT_TYPE_AR) + renderer = desmume_cell_renderer_ndtext_new(); g_object_set(renderer, "editable", TRUE, NULL); g_signal_connect(renderer, "edited", G_CALLBACK(cheat_list_modify_cheat), model); @@ -321,19 +326,25 @@ static void cheat_list_add_columns(GtkTreeView * tree, GtkListStore * store) attrib = "text"; break; } + gint c = columnTable[ii].column; column = gtk_tree_view_column_new_with_attributes(columnTable[ii]. caption, renderer, - attrib, columnTable[ii].column, + attrib, c, NULL); - if (columnTable[ii].column == COLUMN_HI) { - gtk_tree_view_column_set_cell_data_func(column, renderer, - cheat_list_address_to_hex, - NULL, NULL); - } - g_object_set_data(G_OBJECT(renderer), "column", - GINT_TO_POINTER(columnTable[ii].column)); - gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column); + if (c == COLUMN_HI && cheat_type == CHEAT_TYPE_INTERNAL) { + gtk_tree_view_column_set_cell_data_func(column, renderer, + cheat_list_address_to_hex, + NULL, NULL); + } + if (c == COLUMN_ENABLED || c == COLUMN_DESC || + ((c == COLUMN_SIZE || c == COLUMN_HI || c == COLUMN_LO) && + cheat_type == CHEAT_TYPE_INTERNAL) || + (c == COLUMN_AR && cheat_type == CHEAT_TYPE_AR)) { + g_object_set_data(G_OBJECT(renderer), "column", + GINT_TO_POINTER(columnTable[ii].column)); + gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column); + } } } @@ -356,16 +367,35 @@ static GtkListStore *cheat_list_populate() GtkTreeIter iter; cheats->copyItemFromIndex(ii, cheat); gtk_list_store_append(store, &iter); - gtk_list_store_set(store, &iter, - COLUMN_INDEX, ii, - COLUMN_TYPE, cheat.type, - COLUMN_ENABLED, cheat.enabled, - COLUMN_SIZE, cheat.size+1, - COLUMN_AR, g_strdup("00000000 00000000"), - COLUMN_HI, cheat.code[0][0], - COLUMN_LO, cheat.code[0][1], - COLUMN_DESC, cheat.description, - -1); + if (cheat.type == CHEAT_TYPE_INTERNAL) { + gtk_list_store_set(store, &iter, + COLUMN_INDEX, ii, + COLUMN_TYPE, cheat.type, + COLUMN_ENABLED, cheat.enabled, + COLUMN_SIZE, cheat.size+1, + COLUMN_HI, cheat.code[0][0], + COLUMN_LO, cheat.code[0][1], + COLUMN_DESC, cheat.description, + -1); + } else if (cheat.type == CHEAT_TYPE_AR) { + u32 cheat_len = cheat.num; + char *cheat_str = (char *) malloc (18*cheat_len); + cheat_str[0] = '\0'; + + for (u32 jj = 0; jj < cheat_len; jj++) { + gchar *tmp = g_strdup_printf("%08X %08X\n", cheat.code[jj][0], cheat.code[jj][1]); + g_strlcat(cheat_str, tmp, 18*cheat_len); + } + cheat_str[18*cheat_len - 1] = '\0'; // Remove the trailing '\n' + + gtk_list_store_set(store, &iter, + COLUMN_INDEX, ii, + COLUMN_TYPE, cheat.type, + COLUMN_ENABLED, cheat.enabled, + COLUMN_AR, g_strdup(cheat_str), + COLUMN_DESC, cheat.description, + -1); + } } return store; } @@ -419,14 +449,14 @@ static void cheat_list_create_ui() button = gtk_button_new_with_label("Add Action Replay cheat"); gtk_container_add(GTK_CONTAINER(hbbox),button); - g_signal_connect (button, "clicked", G_CALLBACK (cheat_list_add_cheat_ar), store); + g_signal_connect (button, "clicked", G_CALLBACK (cheat_list_add_cheat_AR), store); button = gtk_button_new_with_label("Remove Action Replay cheat"); g_signal_connect (button, "clicked", G_CALLBACK (cheat_list_remove_cheat), GTK_TREE_VIEW(tree_ar)); gtk_container_add(GTK_CONTAINER(hbbox),button); - cheat_list_add_columns(GTK_TREE_VIEW(tree_raw), store); - cheat_list_add_columns(GTK_TREE_VIEW(tree_ar), store); + cheat_list_add_columns(GTK_TREE_VIEW(tree_raw), store, CHEAT_TYPE_INTERNAL); + cheat_list_add_columns(GTK_TREE_VIEW(tree_ar), store, CHEAT_TYPE_AR); /* Setup the selection handler */ GtkTreeSelection *select; diff --git a/desmume/src/frontend/posix/gtk/meson.build b/desmume/src/frontend/posix/gtk/meson.build index 53f4b3883..48f45bac6 100644 --- a/desmume/src/frontend/posix/gtk/meson.build +++ b/desmume/src/frontend/posix/gtk/meson.build @@ -21,6 +21,7 @@ desmume_src = [ 'tools/ioregsView.cpp', 'cheatsGTK.cpp', 'main.cpp', + 'utilsGTK.cpp', gresource, ] diff --git a/desmume/src/frontend/posix/gtk/utilsGTK.cpp b/desmume/src/frontend/posix/gtk/utilsGTK.cpp new file mode 100644 index 000000000..76c71a5d5 --- /dev/null +++ b/desmume/src/frontend/posix/gtk/utilsGTK.cpp @@ -0,0 +1,266 @@ +/* + Copyright (C) 2009-2024 DeSmuME team + + This file is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This file is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with the this software. If not, see . +*/ + +#include "utilsGTK.h" + +/* + A C++ implementation of a GtkCellRendererText subclass which handles + newline-delimited text and allows for editing that text with the ability + for users to add newlines, based off of a GPLv2+ Python implementation here: + https://gitlab.gnome.org/GNOME/gtk/-/issues/175#note_487323 +*/ + +/* + DESMUME_ENTRY_ND: + An object similar to an Entry, but which allows for newlines to be + inserted by holding Shift, Ctrl, or Alt along with pressing Enter. +*/ + +typedef struct { + GtkWidget *scroll; + GtkWidget *editor; + gboolean editing_canceled; +} DesmumeEntryNdPrivate; + +typedef enum { + PROP_EDITING_CANCELED = 1, + ENTRY_ND_NUM_PROP, +} DesmumeEntryNdProperty; + +static GParamSpec *entry_nd_properties[ENTRY_ND_NUM_PROP] = { NULL, }; + +// Declared here to statisfy the type creation macro, but defined further down. +static void desmume_entry_nd_editable_init(GtkEditableInterface *iface); +static void desmume_entry_nd_cell_editable_init(GtkCellEditableIface *iface); + +G_DEFINE_TYPE_WITH_CODE (DesmumeEntryNd, desmume_entry_nd, GTK_TYPE_EVENT_BOX, + G_ADD_PRIVATE (DesmumeEntryNd) + G_IMPLEMENT_INTERFACE (GTK_TYPE_EDITABLE, desmume_entry_nd_editable_init) + G_IMPLEMENT_INTERFACE (GTK_TYPE_CELL_EDITABLE, desmume_entry_nd_cell_editable_init)) + +static void desmume_entry_nd_set_property(GObject *object, guint property_id, + const GValue *value, GParamSpec *pspec) +{ + DesmumeEntryNd *entry_nd = DESMUME_ENTRY_ND (object); + DesmumeEntryNdPrivate *priv; + priv = (DesmumeEntryNdPrivate *)desmume_entry_nd_get_instance_private(entry_nd); + + switch ((DesmumeEntryNdProperty) property_id) { + case PROP_EDITING_CANCELED: + if (priv->editing_canceled != g_value_get_boolean(value)) { + priv->editing_canceled = g_value_get_boolean(value); + g_object_notify(object, "editing-canceled"); + } + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); + break; + } +} + +static void desmume_entry_nd_get_property(GObject *object, guint property_id, + GValue *value, GParamSpec *pspec) +{ + DesmumeEntryNd *entry_nd = DESMUME_ENTRY_ND (object); + DesmumeEntryNdPrivate *priv; + priv = (DesmumeEntryNdPrivate *)desmume_entry_nd_get_instance_private(entry_nd); + + switch ((DesmumeEntryNdProperty) property_id) { + case PROP_EDITING_CANCELED: + g_value_set_boolean(value, priv->editing_canceled); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); + break; + } +} + +static gboolean desmume_entry_nd_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data) +{ + DesmumeEntryNd *entry_nd = (DesmumeEntryNd *) data; + DesmumeEntryNdPrivate *priv; + priv = (DesmumeEntryNdPrivate *)desmume_entry_nd_get_instance_private(entry_nd); + + // Allow the editor to decide how to handle the key event, except key events + // which its parent TextView needs to handle itself + gboolean doPropagate = GDK_EVENT_PROPAGATE; + guint kv = event->keyval; + guint mod = event->state; + + if ((kv == GDK_KEY_Return || kv == GDK_KEY_KP_Enter || kv == GDK_KEY_ISO_Enter) && + !(mod & (GDK_CONTROL_MASK | GDK_SHIFT_MASK | GDK_MOD1_MASK))) { + // Enter + Ctrl, Shift, or Mod1 (commonly Alt), enter a newline in the + // editor, but otherwise act as confirm for the TextView + priv->editing_canceled = FALSE; + doPropagate = GDK_EVENT_STOP; + gtk_cell_editable_editing_done(GTK_CELL_EDITABLE(entry_nd)); + // TODO: Why does GTK have to be so annoying with a critical error on valid data here? :( + //gtk_cell_editable_remove_widget(GTK_CELL_EDITABLE(entry_nd)); + } else if (kv == GDK_KEY_Escape) { + priv->editing_canceled = TRUE; + doPropagate = GDK_EVENT_STOP; + // gtk_cell_editable_editing_done(GTK_CELL_EDITABLE(entry_nd)); + // gtk_cell_editable_remove_widget(GTK_CELL_EDITABLE(entry_nd)); + } + return doPropagate; +} + +static gboolean desmume_entry_nd_button_press(GtkWidget *widget, GdkEventButton *event, gpointer data) +{ + GtkTextView *editor = (GtkTextView *) widget; + GtkWidgetClass *klass = GTK_WIDGET_GET_CLASS(editor); + klass->button_press_event(widget, event); + // We have explicitly how to handle mouse button events, so do not propagate. + return GDK_EVENT_STOP; +} + +static void desmume_entry_nd_class_init(DesmumeEntryNdClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + object_class->set_property = desmume_entry_nd_set_property; + object_class->get_property = desmume_entry_nd_get_property; + + entry_nd_properties[PROP_EDITING_CANCELED] = + g_param_spec_boolean("editing-canceled", "Editing Canceled", + "The edit was canceled", FALSE, G_PARAM_READWRITE); + g_object_class_install_properties (object_class, ENTRY_ND_NUM_PROP, entry_nd_properties); +} + +static void desmume_entry_nd_init(DesmumeEntryNd *entry_nd) +{ + DesmumeEntryNdPrivate *priv; + priv = (DesmumeEntryNdPrivate *)desmume_entry_nd_get_instance_private(entry_nd); + + priv->scroll = gtk_scrolled_window_new(NULL, NULL); + priv->editor = gtk_text_view_new(); + gtk_text_view_set_editable(GTK_TEXT_VIEW(priv->editor), TRUE); + gtk_text_view_set_accepts_tab(GTK_TEXT_VIEW(priv->editor), FALSE); + + g_signal_connect (priv->editor, "key-press-event", + G_CALLBACK (desmume_entry_nd_key_press), entry_nd); + g_signal_connect (priv->editor, "button-press-event", + G_CALLBACK (desmume_entry_nd_button_press), NULL); + + gtk_container_add(GTK_CONTAINER(priv->scroll), priv->editor); + gtk_container_add(GTK_CONTAINER(entry_nd), priv->scroll); +} + +static void desmume_entry_nd_start_editing(GtkCellEditable *cell_editable, GdkEvent *event) +{ + DesmumeEntryNd *entry_nd = DESMUME_ENTRY_ND(cell_editable); + DesmumeEntryNdPrivate *priv; + priv = (DesmumeEntryNdPrivate *)desmume_entry_nd_get_instance_private(entry_nd); + + gtk_widget_show_all(GTK_WIDGET(entry_nd)); + gtk_widget_grab_focus(GTK_WIDGET(priv->editor)); + + // Highlight the entirety of the editor's text + GtkTextBuffer *buf = gtk_text_view_get_buffer (GTK_TEXT_VIEW(priv->editor)); + GtkTextIter start, end; + gtk_text_buffer_get_bounds(buf, &start, &end); + gtk_text_buffer_select_range(buf, &start, &end); +} + +static gchar* desmume_entry_nd_get_text(DesmumeEntryNd *entry_nd) +{ + DesmumeEntryNdPrivate *priv; + priv = (DesmumeEntryNdPrivate *)desmume_entry_nd_get_instance_private(entry_nd); + GtkTextBuffer *buf = gtk_text_view_get_buffer (GTK_TEXT_VIEW(priv->editor)); + GtkTextIter start, end; + gtk_text_buffer_get_bounds(buf, &start, &end); + + return gtk_text_buffer_get_text (buf, &start, &end, TRUE); +} + +static void desmume_entry_nd_set_text(DesmumeEntryNd *entry_nd, const gchar* text) +{ + DesmumeEntryNdPrivate *priv; + priv = (DesmumeEntryNdPrivate *)desmume_entry_nd_get_instance_private(entry_nd); + GtkTextBuffer *buf = gtk_text_view_get_buffer (GTK_TEXT_VIEW(priv->editor)); + gtk_text_buffer_set_text(buf, text, strlen(text)); +} + +static void desmume_entry_nd_editable_init(GtkEditableInterface *iface) {} + +static void desmume_entry_nd_cell_editable_init(GtkCellEditableIface *iface) +{ + iface->start_editing = desmume_entry_nd_start_editing; +} + +GtkWidget *desmume_entry_nd_new() +{ + return (GtkWidget *) g_object_new(DESMUME_TYPE_ENTRY_ND, NULL); +} + +/* + DESMUME_CELL_RENDERER_ND_TEXT: + A subclass of GtkCellRendererText which creates our DesmumeEntryNd instead + of a GtkEntry, which allows a cell in a TreeView to accepts newlines. +*/ + +G_DEFINE_TYPE (DesmumeCellRendererNdtext, desmume_cell_renderer_ndtext, GTK_TYPE_CELL_RENDERER_TEXT) + +static void desmume_cell_renderer_ndtext_editing_done(GtkCellEditable *entry_nd, gpointer data) +{ + gboolean canceled; + g_object_get(entry_nd, "editing-canceled", &canceled, NULL); + if (!canceled) { + const gchar *path = (gchar *) g_object_get_data (G_OBJECT (entry_nd), "full-text"); + const gchar *new_text = desmume_entry_nd_get_text (DESMUME_ENTRY_ND(entry_nd)); + + guint signal_id = g_signal_lookup("edited", DESMUME_TYPE_CELL_RENDERER_NDTEXT); + g_signal_emit(data, signal_id, 0, path, new_text); + } + gtk_cell_editable_remove_widget(GTK_CELL_EDITABLE(entry_nd)); +} + +static GtkCellEditable *desmume_cell_renderer_ndtext_start_editing( + GtkCellRenderer* cell, GdkEvent *event, + GtkWidget *widget, const gchar *path, + const GdkRectangle *background_area, + const GdkRectangle *cell_area, + GtkCellRendererState flags) +{ + DesmumeCellRendererNdtext *ndtext = DESMUME_CELL_RENDERER_NDTEXT (cell); + gboolean editable; + g_object_get(G_OBJECT(ndtext), "editable" , &editable, NULL); + if (!editable) + return NULL; + + gchar *text; + g_object_get(G_OBJECT(ndtext), "text", &text, NULL); + + GtkWidget *entry_nd = desmume_entry_nd_new(); + if (text != NULL) + desmume_entry_nd_set_text(DESMUME_ENTRY_ND (entry_nd), text); + g_object_set_data_full(G_OBJECT (entry_nd), "full-text", g_strdup(path), g_free); + + g_signal_connect(entry_nd, "editing-done", + G_CALLBACK(desmume_cell_renderer_ndtext_editing_done), ndtext); + return GTK_CELL_EDITABLE(entry_nd); +} + +static void desmume_cell_renderer_ndtext_class_init(DesmumeCellRendererNdtextClass *klass) { + GtkCellRendererClass *cell_class = GTK_CELL_RENDERER_CLASS (klass); + cell_class->start_editing = desmume_cell_renderer_ndtext_start_editing; +} +static void desmume_cell_renderer_ndtext_init(DesmumeCellRendererNdtext* ndtext) {} + +GtkCellRenderer *desmume_cell_renderer_ndtext_new() +{ + return GTK_CELL_RENDERER(g_object_new(DESMUME_TYPE_CELL_RENDERER_NDTEXT, NULL)); +} diff --git a/desmume/src/frontend/posix/gtk/utilsGTK.h b/desmume/src/frontend/posix/gtk/utilsGTK.h new file mode 100644 index 000000000..65b9e3019 --- /dev/null +++ b/desmume/src/frontend/posix/gtk/utilsGTK.h @@ -0,0 +1,60 @@ +/* + Copyright (C) 2009-2024 DeSmuME team + + This file is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This file is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with the this software. If not, see . +*/ + +#ifndef __UTILS_GTK_H__ +#define __UTILS_GTK_H__ + +#include + +G_BEGIN_DECLS + +#define DESMUME_TYPE_ENTRY_ND (desmume_entry_nd_get_type ()) +G_DECLARE_FINAL_TYPE(DesmumeEntryNd, desmume_entry_nd, DESMUME, ENTRY_ND, GtkEventBox) + +struct _DesmumeEntryNd +{ + GtkEventBox parent; +}; + +struct _DesmumeEntryNdClass +{ + GtkEventBoxClass *parent_class; +}; + +GType desmume_entry_nd_get_type (void) G_GNUC_CONST; +GtkEventBox *entry_nd_new(void); + +#define DESMUME_TYPE_CELL_RENDERER_NDTEXT (desmume_cell_renderer_ndtext_get_type ()) +G_DECLARE_FINAL_TYPE(DesmumeCellRendererNdtext, desmume_cell_renderer_ndtext, + DESMUME, CELL_RENDERER_NDTEXT, GtkCellRendererText) + +struct _DesmumeCellRendererNdtext +{ + GtkCellRendererText parent; +}; + +struct _DesmumeCellRendererNdtextClass +{ + GtkCellRendererTextClass *parent_class; +}; + +GType desmume_cell_renderer_ndtext_get_type (void) G_GNUC_CONST; +GtkCellRenderer *desmume_cell_renderer_ndtext_new(void); + +G_END_DECLS + +#endif /*__UTILS_GTK_H__*/