diff --git a/gtk3/macros.h b/gtk3/macros.h index 02fec4d..d02ef7a 100644 --- a/gtk3/macros.h +++ b/gtk3/macros.h @@ -5,6 +5,7 @@ #define xstr(x) str(x) #define get_object(id) gtk_builder_get_object(builder, id) #define builder_get(type, id) type(gtk_builder_get_object(builder, id)) +#define action_set_enabled(map, name, value) g_simple_action_set_enabled(G_SIMPLE_ACTION(g_action_map_lookup_action(G_ACTION_MAP(map), name)), value); #define itoa(val) g_strdup_printf("%i", val) #endif /* macros_h */ diff --git a/gtk3/main.c b/gtk3/main.c index 45b3dbc..77d7a6e 100644 --- a/gtk3/main.c +++ b/gtk3/main.c @@ -86,25 +86,39 @@ static GCond debugger_input_cond; static GRecMutex console_output_lock; static GPtrArray *debugger_input_queue; -// List of GActions for the `app` prefix -// TODO: The order of the items in the structure are intended to reflect frequency of use -static const GActionEntry app_entries[] = { - { "quit", activate_quit, NULL, NULL, NULL }, - { "about", activate_about, NULL, NULL, NULL }, +static const GActionEntry file_entries[] = { { "open", activate_open, NULL, NULL, NULL }, + { "close", activate_close, NULL, NULL, NULL }, +}; + +static const GActionEntry edit_entries[] = { + +}; + +static const GActionEntry emulation_entries[] = { + { "reset", activate_reset, NULL, NULL, NULL }, + { "pause", NULL, NULL, "false", on_pause_changed }, + { "save_state", NULL, NULL, NULL, NULL }, + { "load_state", NULL, NULL, NULL, NULL }, +}; + +static const GActionEntry developer_entries[] = { { "show_console", activate_show_console, NULL, NULL, NULL }, { "open_gtk_debugger", activate_open_gtk_debugger, NULL, NULL, NULL }, { "open_memory_viewer", activate_open_memory_viewer, NULL, NULL, NULL }, { "open_vram_viewer", activate_open_vram_viewer, NULL, NULL, NULL }, - { "preferences", activate_preferences, NULL, NULL, NULL }, - { "reset", activate_reset, NULL, NULL, NULL }, { "toggle_developer_mode", NULL, NULL, "false", NULL }, - { "change_model", NULL, "s", "@s 'CGB'", on_model_changed }, - { "toggle_mute", NULL, NULL, "false", on_mute_changed }, - { "pause", NULL, NULL, "false", on_pause_changed }, { "clear_console", activate_clear_console, NULL, NULL, NULL }, }; +static const GActionEntry app_entries[] = { + { "quit", activate_quit, NULL, NULL, NULL }, + { "about", activate_about, NULL, NULL, NULL }, + { "preferences", activate_preferences, NULL, NULL, NULL }, + { "toggle_mute", NULL, NULL, "false", on_mute_changed }, + { "change_model", NULL, "s", "@s 'CGB'", on_model_changed }, +}; + int main(int argc, char *argv[]) { main_thread = g_thread_self(); @@ -739,7 +753,29 @@ static void flip(void) { } static void quit_interrupt(int ignored) { - quit(G_APPLICATION(main_application)); + quit(); +} + +// WHY DO WE NEED SUCH AN UGLY METHOD, GTK?! +static void action_entries_set_enabled(const GActionEntry *entries, unsigned n_entries, bool value) { + // Assumes null-terminated if n_entries == -1 + for (unsigned i = 0; n_entries == -1 ? entries[i].name != NULL : i < n_entries; i++) { + const GActionEntry *entry = &entries[i]; + if (entry->name == NULL) continue; + + action_set_enabled(main_application, entry->name, value); + } +} + +static void create_action_groups(GApplication *app) { + g_action_map_add_action_entries(G_ACTION_MAP(app), emulation_entries, G_N_ELEMENTS(emulation_entries), NULL); + g_action_map_add_action_entries(G_ACTION_MAP(app), developer_entries, G_N_ELEMENTS(developer_entries), NULL); + g_action_map_add_action_entries(G_ACTION_MAP(app), app_entries, G_N_ELEMENTS(app_entries), NULL); + g_action_map_add_action_entries(G_ACTION_MAP(app), file_entries, G_N_ELEMENTS(file_entries), NULL); + g_action_map_add_action_entries(G_ACTION_MAP(app), edit_entries, G_N_ELEMENTS(edit_entries), NULL); + + action_set_enabled(app, "close", false); + action_entries_set_enabled(emulation_entries, G_N_ELEMENTS(emulation_entries), false); } // This functions gets called immediately after registration of the GApplication @@ -760,8 +796,7 @@ static void startup(GApplication *app, gpointer gui_data_gptr) { builder = gtk_builder_new_from_resource(RESOURCE_PREFIX "ui/window.ui"); gtk_builder_connect_signals(builder, NULL); - // Setup application actions - g_action_map_add_action_entries(G_ACTION_MAP(app), app_entries, G_N_ELEMENTS(app_entries), app); + create_action_groups(app); if (gui_data->cli_options.prefix != NULL) { GAction *action = g_action_map_lookup_action(G_ACTION_MAP(main_application), "change_model"); @@ -770,7 +805,7 @@ static void startup(GApplication *app, gpointer gui_data_gptr) { #if NDEBUG // Disable when not compiled in debug mode - g_simple_action_set_enabled(G_SIMPLE_ACTION(g_action_map_lookup_action(G_ACTION_MAP(app), "open_gtk_debugger")), false); + action_set_enabled(app, "open_gtk_debugger", false); // Remove the menubar override gtk_widget_destroy(builder_get(GTK_WIDGET, "menubar_override_selector_label")); @@ -839,16 +874,7 @@ static void startup(GApplication *app, gpointer gui_data_gptr) { g_list_free_full(icon_list, g_object_unref); } -// This function gets called when the GApplication gets activated, i.e. it is ready to show widgets. -static void activate(GApplication *app, gpointer gui_data_gptr) { - GuiData *gui_data = gui_data_gptr; - - // initialize SameBoy core - init(gui_data); - - init_audio(); - init_controllers(); - +static void connect_signal_handlers(GApplication *app) { // Connect signal handlers gtk_widget_add_events(GTK_WIDGET(main_window), GDK_KEY_PRESS_MASK); gtk_widget_add_events(GTK_WIDGET(main_window), GDK_KEY_RELEASE_MASK); @@ -878,7 +904,9 @@ static void activate(GApplication *app, gpointer gui_data_gptr) { g_signal_connect(get_object("vram_viewer_tilemap_canvas"), "motion_notify_event", G_CALLBACK(on_motion_vram_viewer_tilemap), NULL); g_signal_connect(get_object("vram_viewer_stack"), "notify::visible-child", G_CALLBACK(on_vram_tab_change), NULL); +} +static void create_canvas() { // create our renderer area if (supports_gl) { gl_area = GTK_GL_AREA(gtk_gl_area_new()); @@ -898,7 +926,9 @@ static void activate(GApplication *app, gpointer gui_data_gptr) { GtkCssProvider *provider = gtk_css_provider_new(); gtk_css_provider_load_from_resource(provider, RESOURCE_PREFIX "css/main.css"); gtk_style_context_add_provider_for_screen(screen, GTK_STYLE_PROVIDER(provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); +} +static void setup_console() { GtkTextView *text_view = builder_get(GTK_TEXT_VIEW, "console_screen"); GtkTextBuffer *text_buf = gtk_text_view_get_buffer(text_view); @@ -911,13 +941,6 @@ static void activate(GApplication *app, gpointer gui_data_gptr) { gtk_text_buffer_create_tag(text_buf, "underline", "underline", PANGO_UNDERLINE_SINGLE, "underline-set", TRUE, NULL); gtk_text_buffer_create_tag(text_buf, "dashed_underline", "underline", PANGO_UNDERLINE_DOUBLE, "underline-set", TRUE, NULL); - if (gui_data->cli_options.fullscreen) { - gtk_window_fullscreen(GTK_WINDOW(main_window)); - } - - gtk_application_add_window(GTK_APPLICATION(app), GTK_WINDOW(main_window)); - gtk_widget_show_all(GTK_WIDGET(main_window)); - g_mutex_init(&debugger_input_mutex); g_cond_init(&debugger_input_cond); g_rec_mutex_init(&console_output_lock); @@ -929,6 +952,28 @@ static void activate(GApplication *app, gpointer gui_data_gptr) { if (!pending_console_output) { pending_console_output = gtk_text_buffer_new(gtk_text_buffer_get_tag_table(text_buf)); } +} + +// This function gets called when the GApplication gets activated, i.e. it is ready to show widgets. +static void activate(GApplication *app, gpointer gui_data_gptr) { + GuiData *gui_data = gui_data_gptr; + + // initialize SameBoy core + init(gui_data); + + init_audio(); + init_controllers(); + + connect_signal_handlers(app); + create_canvas(); + setup_console(); + + if (gui_data->cli_options.fullscreen) { + gtk_window_fullscreen(GTK_WINDOW(main_window)); + } + + gtk_application_add_window(GTK_APPLICATION(app), GTK_WINDOW(main_window)); + gtk_widget_show_all(GTK_WIDGET(main_window)); // Start the emulation thread run(gui_data); @@ -976,16 +1021,12 @@ static void open(GApplication *app, GFile **files, gint n_files, const gchar *hi // After this functions has been called the `shutdown` signal will be issued. // // TODO: Make sure we have a way to quit our emulation loop before `shutdown` gets called -static void quit(GApplication *app) { - // Tell our own main loop to quit. - // This will allow our run() and therefore our activate() methods to end. - running = false; +static void quit() { + stop(&gui_data); - if (app) { - // Quit our application properly. - // This fires the “shutdown” signal. - g_application_quit(app); - } + // Quit our application properly. + // This fires the “shutdown” signal. + g_application_quit(G_APPLICATION(main_application)); } static gboolean on_key_press(GtkWidget *w, GdkEventKey *event, gpointer data) { @@ -1092,6 +1133,8 @@ static void activate_open_vram_viewer(GSimpleAction *action, GVariant *parameter // app.open GAction // Opens a ROM file static void activate_open(GSimpleAction *action, GVariant *parameter, gpointer app) { + stop(&gui_data); + GtkFileChooserNative *native = gtk_file_chooser_native_new("Open File", GTK_WINDOW(main_window), GTK_FILE_CHOOSER_ACTION_OPEN, "_Open", "_Cancel"); gint res = gtk_native_dialog_run(GTK_NATIVE_DIALOG(native)); @@ -1101,10 +1144,33 @@ static void activate_open(GSimpleAction *action, GVariant *parameter, gpointer a activate_reset(action, parameter, app); } + else { + run(&gui_data); + } g_object_unref(native); } +// app.close GAction +// Closes a ROM +static void activate_close(GSimpleAction *action, GVariant *parameter, gpointer app) { + stop(&gui_data); + GB_free(&gb); + + // Clear the screen as side effect + update_window_geometry(); + + if (fallback_canvas) { + gtk_widget_queue_draw(GTK_WIDGET(main_window)); + } + else if (gl_area) { + gtk_gl_area_queue_render(gl_area); + } + + action_set_enabled(main_application, "close", false); + action_entries_set_enabled(emulation_entries, G_N_ELEMENTS(emulation_entries), false); +} + // app.preferences GAction // Opens the preferences window static void activate_preferences(GSimpleAction *action, GVariant *parameter, gpointer app) { @@ -1114,20 +1180,19 @@ static void activate_preferences(GSimpleAction *action, GVariant *parameter, gpo // app.quit GAction // Exits the application static void activate_quit(GSimpleAction *action, GVariant *parameter, gpointer app) { - quit(G_APPLICATION(app)); + quit(); } // app.reset GAction // Resets the emulation static void activate_reset(GSimpleAction *action, GVariant *parameter, gpointer app) { - if (gui_data.stopped) { - reset(&gui_data); - } - else { - stop(&gui_data); - reset(&gui_data); - run(&gui_data); + if (!GB_is_inited(&gb)) { + init(&gui_data); } + + stop(&gui_data); + reset(&gui_data); + run(&gui_data); } // app.clear_console GAction @@ -1206,7 +1271,7 @@ static void on_pause_changed(GSimpleAction *action, GVariant *value, gpointer us // `destroy` signal GCallback // Exits the application static void on_quit(GtkWidget *w, gpointer app) { - quit(G_APPLICATION(app)); + quit(); } // TODO: Comment @@ -2093,7 +2158,7 @@ static gpointer run_thread(gpointer gui_data_gptr) { GuiData *gui_data = gui_data_gptr; if (!gui_data->file) return NULL; - + if (gui_data->stopped) { start(gui_data); } @@ -2105,6 +2170,7 @@ static gpointer run_thread(gpointer gui_data_gptr) { return NULL; } + static void init(GuiData *gui_data) { if (GB_is_inited(&gb)) return; @@ -2234,13 +2300,22 @@ static void reset(GuiData *gui_data) { update_window_geometry(); } - char *path = g_file_get_path(gui_data->file); + bool success = false; + if (gui_data->file) { + char *path = g_file_get_path(gui_data->file); - if (GB_load_rom(&gb, path) != 0) { - g_warning("Failed to load ROM: %s", path); + if (GB_load_rom(&gb, path) == 0) { + success = true; + } + else { + g_warning("Failed to load ROM: %s", path); + } + + g_free(path); } - g_free(path); + action_set_enabled(main_application, "close", success); + action_entries_set_enabled(emulation_entries, G_N_ELEMENTS(emulation_entries), success); } static void start(GuiData *gui_data) { diff --git a/gtk3/main.h b/gtk3/main.h index a4250d8..6775440 100644 --- a/gtk3/main.h +++ b/gtk3/main.h @@ -95,7 +95,7 @@ static void startup(GApplication *app, gpointer user_data_gptr); static void activate(GApplication *app, gpointer user_data_gptr); static void shutdown(GApplication *app, GFile **files, gint n_files, const gchar *hint, gpointer user_data_gptr); static void open(GApplication *app, GFile **files, gint n_files, const gchar *hint, gpointer user_data_gptr); -static void quit(GApplication *app); +static void quit(); static gboolean on_key_press(GtkWidget *w, GdkEventKey *event, gpointer data); static void on_window_state_change(GtkWidget *w, GdkEventWindowState *event, gpointer data); @@ -106,6 +106,7 @@ static void activate_open_gtk_debugger(GSimpleAction *action, GVariant *paramete static void activate_open_memory_viewer(GSimpleAction *action, GVariant *parameter, gpointer app); static void activate_open_vram_viewer(GSimpleAction *action, GVariant *parameter, gpointer app); static void activate_open(GSimpleAction *action, GVariant *parameter, gpointer app); +static void activate_close(GSimpleAction *action, GVariant *parameter, gpointer app); static void activate_preferences(GSimpleAction *action, GVariant *parameter, gpointer app); static void activate_quit(GSimpleAction *action, GVariant *parameter, gpointer app); static void activate_reset(GSimpleAction *action, GVariant *parameter, gpointer app); diff --git a/gtk3/resources/gtk/menus-common.ui b/gtk3/resources/gtk/menus-common.ui index 3e76c6c..b07dd8d 100644 --- a/gtk3/resources/gtk/menus-common.ui +++ b/gtk3/resources/gtk/menus-common.ui @@ -229,7 +229,7 @@ Author: Maximilian Mader - Save State + Load State Slot 1 0