diff --git a/gtk3/check_menu_radio_group.c b/gtk3/check_menu_radio_group.c new file mode 100644 index 0000000..3f4c9d2 --- /dev/null +++ b/gtk3/check_menu_radio_group.c @@ -0,0 +1,79 @@ +#include "check_menu_radio_group.h" + +void check_menu_item_group_handler(GtkCheckMenuItem *item, CheckMenuItemGroupHandlerData *data) { + bool cancel = false; + + if (data->handler) { + cancel = data->handler(GTK_WIDGET(item), (gpointer) data->arg); + } + + GValue value = G_VALUE_INIT; + g_value_init(&value, G_TYPE_BOOLEAN); + + if (cancel) { + g_value_set_boolean(&value, false); + g_object_set_property(G_OBJECT(item), "active", &value); + } + else { + for (unsigned i = 0; i < data->group->count; i++) { + GtkCheckMenuItem *cur = GTK_CHECK_MENU_ITEM(data->group->items[i]); + g_value_set_boolean(&value, cur == item); + g_object_set_property(G_OBJECT(cur), "active", &value); + } + } +} + +CheckMenuItemGroup *check_menu_item_group_new(char **names, char **args) { + unsigned name_count = 0; + + if (names != NULL) { + for (char **ptr = names; *ptr != NULL; ptr++, name_count++); + } + + CheckMenuItemGroup *group = g_malloc0(sizeof(CheckMenuItemGroup)); + group->count = name_count; + group->items = g_malloc0(sizeof(GtkWidget*) * name_count); + group->handlers = g_malloc0(sizeof(CheckMenuItemGroupHandlerData*) * name_count); + + for (unsigned i = 0; i < name_count; i++) { + group->items[i] = gtk_check_menu_item_new_with_label(names[i]); + + group->handlers[i] = g_malloc0(sizeof(CheckMenuItemGroupHandlerData)); + group->handlers[i]->group = group; + group->handlers[i]->arg = args[i]; + g_signal_connect(group->items[i], "toggled", G_CALLBACK(check_menu_item_group_handler), group->handlers[i]); + + gtk_check_menu_item_set_draw_as_radio(GTK_CHECK_MENU_ITEM(group->items[i]), true); + } + + return group; +} + +void check_menu_item_group_activate(CheckMenuItemGroup *group, char *arg) { + GValue value = G_VALUE_INIT; + g_value_init(&value, G_TYPE_BOOLEAN); + g_value_set_boolean(&value, false); + + for (unsigned i = 0; i < group->count; i++) { + GtkCheckMenuItem *cur = GTK_CHECK_MENU_ITEM(group->items[i]); + + if (g_strcmp0(arg, group->handlers[i]->arg) == 0) { + gtk_check_menu_item_set_active(cur, true); + } + else { + g_object_set_property(G_OBJECT(cur), "active", &value); + } + } +} + +void check_menu_item_group_connect_toggle_signal(CheckMenuItemGroup *group, bool (*handler)(GtkWidget *, gpointer)) { + for (unsigned i = 0; i < group->count; i++) { + group->handlers[i]->handler = handler; + } +} + +void check_menu_item_group_insert_into_menu_shell(CheckMenuItemGroup *group, GtkMenuShell *menu_shell, gint position) { + for (unsigned i = 0; i < group->count; i++) { + gtk_menu_shell_insert(menu_shell, group->items[i], position + i); + } +} diff --git a/gtk3/check_menu_radio_group.h b/gtk3/check_menu_radio_group.h new file mode 100644 index 0000000..de12df9 --- /dev/null +++ b/gtk3/check_menu_radio_group.h @@ -0,0 +1,24 @@ +#ifndef check_menu_radio_group_h +#define check_menu_radio_group_h + +#include +#include + +typedef struct CheckMenuItemGroupHandlerData { + struct CheckMenuItemGroup *group; + char *arg; + bool (*handler)(GtkWidget *, void *); +} CheckMenuItemGroupHandlerData; + +typedef struct CheckMenuItemGroup { + unsigned count; + GtkWidget **items; + CheckMenuItemGroupHandlerData **handlers; +} CheckMenuItemGroup; + +CheckMenuItemGroup *check_menu_item_group_new(char **names, char **args); +void check_menu_item_group_activate(CheckMenuItemGroup *group, char *arg); +void check_menu_item_group_connect_toggle_signal(CheckMenuItemGroup *group, bool (*handler)(GtkWidget *, gpointer)); +void check_menu_item_group_insert_into_menu_shell(CheckMenuItemGroup *group, GtkMenuShell *menu_shell, gint position); + +#endif \ No newline at end of file diff --git a/gtk3/main.c b/gtk3/main.c index bcd01a7..4b820f5 100644 --- a/gtk3/main.c +++ b/gtk3/main.c @@ -11,6 +11,7 @@ #include "settings.h" #include "shader.h" +#include "check_menu_radio_group.h" // used for audio and game controllers #include "SDL.h" @@ -822,20 +823,22 @@ GtkWidget *menubar_to_menu(GtkMenuBar *menubar) { // environments and the manual call of `g_signal_connect` was needed anyway // because the UI definition can’t define string arguments for signal handlers. static void create_model_menu_items() { - void on_change_model(GtkCheckMenuItem *check_menu_item, const gchar *model_str); + bool on_change_model(GtkWidget *, gpointer); static const char *const model_names[] = { "Game Boy", "Super Game Boy", "Game Boy Color", - "Game Boy Advance" + "Game Boy Advance", + NULL }; static const char *const model_codes[] = { "DMG", "SGB", "CGB", - "GBA" + "GBA", + NULL }; // Find the menu item index of the previous sibling of the new menu items @@ -844,48 +847,27 @@ static void create_model_menu_items() { g_autoptr(GList) list = gtk_container_get_children(parent); gint position = g_list_index(list, before); - GSList *group = NULL; - for (int i = 0; i < sizeof(model_names) / sizeof(const char*); i++) { - // Create a new menu item - GtkWidget *item = gtk_radio_menu_item_new_with_label(group, model_names[i]); - - // Add it to the existing menu - gtk_menu_shell_insert(GTK_MENU_SHELL(parent), item, ++position); - g_signal_connect(item, "toggled", G_CALLBACK(on_change_model), (gpointer) model_codes[i]); - - if (g_strcmp0(config.emulation.model, model_codes[i]) == 0) { - gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), true); - } - - if (i == 0) group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(item)); - } + CheckMenuItemGroup *model_group = check_menu_item_group_new((char **) model_names, (char **) model_codes); + check_menu_item_group_insert_into_menu_shell(model_group, GTK_MENU_SHELL(parent), position + 1); + check_menu_item_group_connect_toggle_signal(model_group, on_change_model); + check_menu_item_group_activate(model_group, config.emulation.model); static const char *const peripheral_names[] = { "None", "Game Boy Printer", + NULL }; static const char *const peripheral_codes[] = { "NONE", "PRINTER", + NULL, }; - GtkMenuShell *link_menu = builder_get(GTK_MENU_SHELL, "link_menu"); - group = NULL; - position = 0; - for (int i = 0; i < sizeof(peripheral_names) / sizeof(const char*); i++) { - // Create a new menu item - GtkWidget *item = gtk_radio_menu_item_new_with_label(group, peripheral_names[i]); - - // Add it to the existing menu - gtk_menu_shell_insert(link_menu, item, position++); - // g_signal_connect(item, "toggled", G_CALLBACK(on_change_linked_device, (gpointer) peripheral_codes[i]); - - if (i == 0) { - group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(item)); - gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), true); - } - } + CheckMenuItemGroup *link_group = check_menu_item_group_new((char **) peripheral_names, (char **) peripheral_codes); + check_menu_item_group_insert_into_menu_shell(link_group, GTK_MENU_SHELL(builder_get(GTK_MENU_SHELL, "link_menu")), 0); + // check_menu_item_group_connect_toggle_signal(link_group, on_change_linked_device); + check_menu_item_group_activate(link_group, "NONE"); } // Create our application’s menu. @@ -2100,9 +2082,18 @@ G_MODULE_EXPORT void on_quit_activate(GtkWidget *w, gpointer user_data_ptr) { quit(); } -G_MODULE_EXPORT void on_change_model(GtkCheckMenuItem *check_menu_item, const gchar *model_str) { - if (!GB_is_inited(&gb) || !gtk_check_menu_item_get_active(check_menu_item)) { - return; +bool on_change_model(GtkWidget *widget, gpointer user_data) { + GtkCheckMenuItem *check_menu_item = GTK_CHECK_MENU_ITEM(widget); + gchar *model_str = (gchar *) user_data; + + if (!gtk_check_menu_item_get_active(check_menu_item)) { + return true; + } + else if (!GB_is_inited(&gb)) { + gui_data.cli_options.model = -1; + config.emulation.model = g_strdup(model_str); + + return false; } GtkMessageDialog *dialog = GTK_MESSAGE_DIALOG(gtk_message_dialog_new( @@ -2121,8 +2112,6 @@ G_MODULE_EXPORT void on_change_model(GtkCheckMenuItem *check_menu_item, const gc // Reset the CLI model override gui_data.cli_options.model = -1; - // g_simple_action_set_state(action, value); - g_free(config.emulation.model); config.emulation.model = g_strdup(model_str); @@ -2135,6 +2124,8 @@ G_MODULE_EXPORT void on_change_model(GtkCheckMenuItem *check_menu_item, const gc run(); gtk_widget_destroy(GTK_WIDGET(dialog)); + + return result != GTK_RESPONSE_YES; } static void connect_signal_handlers(GApplication *app) {