diff --git a/gtk3/main.c b/gtk3/main.c index 4d3ea40..1b4a61d 100644 --- a/gtk3/main.c +++ b/gtk3/main.c @@ -220,7 +220,6 @@ static void activate_about(GSimpleAction *action, GVariant *parameter, gpointer static void activate_preferences(GSimpleAction *action, GVariant *parameter, gpointer app); static void on_pause_changed(GSimpleAction *action, GVariant *value, gpointer user_data_ptr); static void on_mute_changed(GSimpleAction *action, GVariant *value, gpointer user_data_ptr); -static void on_model_changed(GSimpleAction *action, GVariant *value, gpointer user_data_ptr); static const GActionEntry file_entries[] = { { "open", activate_open, NULL, NULL, NULL }, @@ -241,7 +240,7 @@ static const GActionEntry emulation_entries[] = { 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_memory_viewer", activate_open_memory_viewer, NULL, NULL, NULL }, { "open_vram_viewer", activate_open_vram_viewer, NULL, NULL, NULL }, { "toggle_developer_mode", NULL, NULL, "false", NULL }, { "clear_console", activate_clear_console, NULL, NULL, NULL }, @@ -252,9 +251,9 @@ static const GActionEntry app_entries[] = { { "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 }, }; + static void replace_extension(const char *src, size_t length, char *dest, const char *ext) { memcpy(dest, src, length); dest[length] = 0; @@ -817,14 +816,17 @@ static char *async_console_input(GB_gameboy_t *gb) { return input; } -// Returns a `GApplication`s `GMenuModel` by ID -// GApplication menus are loaded from `gtk/menus.ui`, `gtk/menus-traditional.ui` and `gtk/menus-common.ui`. -static GMenuModel *get_menu_model(GApplication *app, const char *id) { - GMenu *menu; +GtkWidget *menubar_to_menu(GtkMenuBar *menubar) { + GtkWidget *menu = gtk_menu_new(); + g_autoptr(GList) iter = gtk_container_get_children(GTK_CONTAINER (menubar)); + + while (iter) { + GtkWidget *item = GTK_WIDGET(iter->data); + gtk_widget_reparent(item, menu); + iter = g_list_next(iter); + } - menu = gtk_application_get_menu_by_id(GTK_APPLICATION(app), id); - - return menu ? G_MENU_MODEL(g_object_ref_sink(menu)) : NULL; + return menu; } // Create our application’s menu. @@ -834,7 +836,7 @@ static GMenuModel *get_menu_model(GApplication *app, const char *id) { // the desktop environment shell handle the menu if it signals support for it // or uses a standard menubar inside the window. static void setup_menu(GApplication *app) { - GMenuModel *menubar_model = get_menu_model(app, "menubar"); + GtkMenuBar *menubar = builder_get(GTK_MENU_BAR, "main_menu"); enum menubar_type_t menubar_type = get_show_menubar(); // Try to use a sane default @@ -878,13 +880,8 @@ static void setup_menu(GApplication *app) { break; case MENUBAR_SHOW_IN_SHELL: - g_debug("Showing menu in the shell"); - gtk_application_set_menubar(GTK_APPLICATION(app), menubar_model); - break; - case MENUBAR_SHOW_IN_WINDOW: { g_debug("Showing menu in the window"); - GtkMenuBar *menubar = GTK_MENU_BAR(gtk_menu_bar_new_from_model(menubar_model)); gtk_box_pack_start(GTK_BOX(gui_data.main_window_container), GTK_WIDGET(menubar), false, false, 0); break; } @@ -901,7 +898,7 @@ static void setup_menu(GApplication *app) { // Hook menubar up to the hamburger button GtkMenuButton *hamburger_button = GTK_MENU_BUTTON(get_object("hamburger_button")); - gtk_menu_button_set_menu_model(hamburger_button, menubar_model); + gtk_menu_button_set_popup(hamburger_button, GTK_WIDGET(menubar_to_menu(menubar))); break; } } @@ -1490,8 +1487,19 @@ static void reset(void) { bool success = false; if (gui_data.file) { char *path = g_file_get_path(gui_data.file); + char *ext = strrchr(path, '.'); + int result; - if (GB_load_rom(&gb, path) == 0) { + GB_debugger_clear_symbols(&gb); + + if (g_strcmp0(ext + 1, "isx") == 0) { + result = GB_load_isx(&gb, path); + } + else { + result = GB_load_rom(&gb, path); + } + + if (result == 0) { success = true; } else { @@ -1643,14 +1651,6 @@ static void quit_interrupt(int ignored) { quit(); } -// `destroy` signal GCallback -// Exits the application -static void on_quit(GtkWidget *w, gpointer app) { - g_debug("on_quit(%p, %p);", w, &app); - - quit(); -} - 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); @@ -1990,8 +1990,10 @@ static void startup(GApplication *app, gpointer null_ptr) { create_action_groups(app); if (gui_data.cli_options.prefix != NULL) { - GAction *action = g_action_map_lookup_action(G_ACTION_MAP(gui_data.main_application), "change_model"); - g_action_change_state(action, g_variant_new_string(gui_data.cli_options.prefix)); + // TODO + + //GAction *action = g_action_map_lookup_action(G_ACTION_MAP(gui_data.main_application), "change_model"); + //g_action_change_state(action, g_variant_new_string(gui_data.cli_options.prefix)); } #if NDEBUG @@ -2056,7 +2058,7 @@ static void startup(GApplication *app, gpointer null_ptr) { } // Let GTK choose the proper icon - gtk_window_set_icon_list(GTK_WINDOW(gui_data.main_window), icon_list); + gtk_window_set_default_icon_list(icon_list); // Add missing information to the about dialog GtkAboutDialog *about_dialog = GTK_ABOUT_DIALOG(get_object("about_dialog")); @@ -2065,12 +2067,53 @@ static void startup(GApplication *app, gpointer null_ptr) { g_list_free_full(icon_list, g_object_unref); } +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; + } + + GtkMessageDialog *dialog = GTK_MESSAGE_DIALOG(gtk_message_dialog_new( + GTK_WINDOW(gui_data.main_window), + GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_QUESTION, + GTK_BUTTONS_YES_NO, + "Changing the emulated model requires a reset.\nChange model and reset game?" + )); + + stop(); + gint result = gtk_dialog_run(GTK_DIALOG(dialog)); + + switch (result) { + case GTK_RESPONSE_YES: + // 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); + + reset(); + break; + default: + // Action has been canceled + break; + } + + run(); + gtk_widget_destroy(GTK_WIDGET(dialog)); +} + static void connect_signal_handlers(GApplication *app) { // Connect signal handlers gtk_widget_add_events(GTK_WIDGET(gui_data.main_window), GDK_KEY_PRESS_MASK); gtk_widget_add_events(GTK_WIDGET(gui_data.main_window), GDK_KEY_RELEASE_MASK); - g_signal_connect(gui_data.main_window, "destroy", G_CALLBACK(on_quit), app); + g_signal_connect(gui_data.main_window, "destroy", G_CALLBACK(on_quit_activate), app); g_signal_connect(gui_data.main_window, "key_press_event", G_CALLBACK(on_key_press), NULL); g_signal_connect(gui_data.main_window, "key_release_event", G_CALLBACK(on_key_press), NULL); g_signal_connect(gui_data.main_window, "window-state-event", G_CALLBACK(on_window_state_change), NULL); @@ -2095,6 +2138,12 @@ static void connect_signal_handlers(GApplication *app) { 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); + + // We can’t set these values in the UI definition file + g_signal_connect(get_object("change_model_dmg"), "toggled", G_CALLBACK(on_change_model), (gpointer) "DMG"); + g_signal_connect(get_object("change_model_sgb"), "toggled", G_CALLBACK(on_change_model), (gpointer) "SGB"); + g_signal_connect(get_object("change_model_cgb"), "toggled", G_CALLBACK(on_change_model), (gpointer) "CGB"); + g_signal_connect(get_object("change_model_agb"), "toggled", G_CALLBACK(on_change_model), (gpointer) "AGB"); } static void update_viewport(void) { @@ -2388,6 +2437,47 @@ static void activate_open_vram_viewer(GSimpleAction *action, GVariant *parameter gtk_widget_show_all(GTK_WIDGET(gui_data.vram_viewer)); } +// Closes a ROM +static void close_rom(void) { + stop(); + GB_free(&gb); + + // Clear the screen as side effect + update_window_geometry(); + + if (gui_data.fallback_canvas) { + gtk_widget_queue_draw(GTK_WIDGET(gui_data.main_window)); + } + else if (gui_data.gl_area) { + gtk_gl_area_queue_render(gui_data.gl_area); + } + + // Clear the VRAM viewer + g_mutex_lock(&gui_data.tileset_buffer_mutex); + memset(gui_data.tileset_buffer, 0, sizeof gui_data.tileset_buffer); + g_mutex_unlock(&gui_data.tileset_buffer_mutex); + + g_mutex_lock(&gui_data.tilemap_buffer_mutex); + memset(gui_data.tilemap_buffer, 0, sizeof gui_data.tilemap_buffer); + g_mutex_unlock(&gui_data.tilemap_buffer_mutex); + + gtk_stack_set_visible_child_name(builder_get(GTK_STACK, "vram_viewer_stack"), "vram_viewer_tileset"); + gtk_tree_view_set_model(builder_get(GTK_TREE_VIEW, "vram_viewer_sprites"), NULL); + gtk_tree_view_set_model(builder_get(GTK_TREE_VIEW, "vram_viewer_palettes"), NULL); + + // Redraw the VRAM viewer + gtk_widget_queue_draw(GTK_WIDGET(gui_data.vram_viewer)); + + // Update menu action states + action_set_enabled(gui_data.main_application, "close", false); + action_entries_set_enabled(emulation_entries, G_N_ELEMENTS(emulation_entries), false); + + // Try force the queued redraws + while (g_main_context_pending(NULL)) { + g_main_context_iteration(NULL, FALSE); + } +} + // app.open GAction // Opens a ROM file static void activate_open(GSimpleAction *action, GVariant *parameter, gpointer app) { @@ -2410,34 +2500,8 @@ static void activate_open(GSimpleAction *action, GVariant *parameter, gpointer a } // app.close GAction -// Closes a ROM static void activate_close(GSimpleAction *action, GVariant *parameter, gpointer app) { - stop(); - GB_free(&gb); - - // Clear the screen as side effect - update_window_geometry(); - gtk_widget_queue_draw(gui_data.fallback_canvas ? GTK_WIDGET(gui_data.fallback_canvas) : GTK_WIDGET(gui_data.gl_area)); - - // Clear the VRAM viewer - g_mutex_lock(&gui_data.tileset_buffer_mutex); - memset(gui_data.tileset_buffer, 0, sizeof gui_data.tileset_buffer); - g_mutex_unlock(&gui_data.tileset_buffer_mutex); - - g_mutex_lock(&gui_data.tilemap_buffer_mutex); - memset(gui_data.tilemap_buffer, 0, sizeof gui_data.tilemap_buffer); - g_mutex_unlock(&gui_data.tilemap_buffer_mutex); - - gtk_stack_set_visible_child_name(builder_get(GTK_STACK, "vram_viewer_stack"), "vram_viewer_tileset"); - gtk_tree_view_set_model(builder_get(GTK_TREE_VIEW, "vram_viewer_sprites"), NULL); - gtk_tree_view_set_model(builder_get(GTK_TREE_VIEW, "vram_viewer_palettes"), NULL); - - // Redraw the VRAM viewer - gtk_widget_queue_draw(GTK_WIDGET(gui_data.vram_viewer)); - - // Update menu action states - action_set_enabled(gui_data.main_application, "close", false); - action_entries_set_enabled(emulation_entries, G_N_ELEMENTS(emulation_entries), false); + close_rom(); } // app.preferences GAction @@ -2464,46 +2528,6 @@ static void activate_clear_console(GSimpleAction *action, GVariant *parameter, g g_rec_mutex_unlock(&gui_data.console_output_lock); } -static void on_model_changed(GSimpleAction *action, GVariant *value, gpointer user_data_ptr) { - if (!GB_is_inited(&gb)) { - g_simple_action_set_state(action, value); - return; - } - - const gchar *model_str = g_variant_get_string(value, NULL); - - GtkMessageDialog *dialog = GTK_MESSAGE_DIALOG(gtk_message_dialog_new( - GTK_WINDOW(gui_data.main_window), - GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, - GTK_MESSAGE_QUESTION, - GTK_BUTTONS_YES_NO, - "Changing the emulated model requires a reset.\nChange model and reset game?" - )); - - stop(); - gint result = gtk_dialog_run(GTK_DIALOG(dialog)); - - switch (result) { - case GTK_RESPONSE_YES: - // 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(g_variant_get_string(value, NULL)); - - reset(); - break; - default: - // Action has been canceled - break; - } - - run(); - gtk_widget_destroy(GTK_WIDGET(dialog)); -} - static void on_mute_changed(GSimpleAction *action, GVariant *value, gpointer user_data_ptr) { config.audio.muted = g_variant_get_boolean(value); @@ -2522,6 +2546,29 @@ static void on_pause_changed(GSimpleAction *action, GVariant *value, gpointer us g_simple_action_set_state(action, value); } +G_MODULE_EXPORT void on_open_recent_activate(GtkRecentChooser *chooser, gpointer user_data_ptr) { + stop(); + + gchar *uri = gtk_recent_chooser_get_current_uri(chooser); + GFile *file = g_file_new_for_uri(uri); + + if (g_file_query_exists(file, NULL)) { + gui_data.file = file; + + // Add the file back to the top of the list + GtkRecentManager *manager = gtk_recent_manager_get_default(); + gtk_recent_manager_add_item(manager, uri); + + // TODO: Not nice + activate_reset(NULL, NULL, NULL); + } + else { + // TODO + g_warning("File not found: %s", uri); + close_rom(); + } +} + G_MODULE_EXPORT void on_boot_rom_location_changed(GtkWidget *w, gpointer user_data_ptr) { GtkComboBox *box = GTK_COMBO_BOX(w); const gchar *id = gtk_combo_box_get_active_id(box); diff --git a/gtk3/resources/ui/window.ui b/gtk3/resources/ui/window.ui index a8039ff..645ed51 100644 --- a/gtk3/resources/ui/window.ui +++ b/gtk3/resources/ui/window.ui @@ -1,5 +1,5 @@ - - + @@ -46,9 +46,6 @@ GTK3 frontend by Maximilian Mader https://github.com/max-m mit-x11 - - - False @@ -70,6 +67,9 @@ Maximilian Mader https://github.com/max-m + + + @@ -118,9 +118,6 @@ Maximilian Mader https://github.com/max-m Debug Console 920 400 - - - True @@ -262,8 +259,6 @@ Maximilian Mader https://github.com/max-m True True - 3 - 3 3 3 Console input @@ -277,6 +272,9 @@ Maximilian Mader https://github.com/max-m + + + @@ -317,9 +315,6 @@ Maximilian Mader https://github.com/max-m False Preferences False - - - True @@ -1207,6 +1202,9 @@ Maximilian Mader https://github.com/max-m + + + True @@ -1231,9 +1229,6 @@ Maximilian Mader https://github.com/max-m False Memory Viewer - - - True @@ -1278,12 +1273,35 @@ Maximilian Mader https://github.com/max-m + + + False False 320 432 + + + True + True + in + + + True + False + none + + + True + False + + + + + + True @@ -1318,20 +1336,359 @@ Maximilian Mader https://github.com/max-m + + + + application/x-gameboy-rom + application/x-gameboy-color-rom + application/octet-stream + + + *.gb + *.gbc + *.isx + + + sameboy + + + + True + False - + True - True - in - - + False + _File + True + + True False - none - + True False + app.open + _Open + True + + + + + True + False + _Recent files + True + + + True + False + recent_files_filter + 10 + mru + + + + + + + + True + False + app.close + _Close + True + + + + + True + False + + + + + True + False + app.quit + _Quit + True + + + + + + + + + True + False + _Edit + True + + + True + False + + + True + False + app.preferences + _Preferences + True + + + + + + + + + True + False + E_mulation + True + + + True + False + + + True + False + app.reset + _Reset + True + + + + + True + False + app.pause + Pause + True + + + + + True + False + + + + + True + False + app.save_state + Save State + True + + + + + True + False + app.load_state + Load State + True + + + + + True + False + + + + + True + False + Game Boy + True + + + + + True + False + Super Game Boy + True + change_model_dmg + + + + + True + False + Game Boy Color + True + change_model_dmg + + + + + True + False + Game Boy Advance + True + change_model_dmg + + + + + True + False + + + + + True + False + app.toggle_mute + Mute Sound + True + + + + + + + + + True + False + _Link + True + + + True + False + + + True + False + None + True + + + + + True + False + Game Boy Printer + True + change_serial_device_none + + + + + + + + + True + False + _Develop + True + + + True + False + + + True + False + app.show_console + Show Console + True + + + + + True + False + app.clear_console + Clear Console + True + + + + + True + False + + + + + True + False + app.break_debugger + Break Debugger + True + + + + + True + False + + + + + True + False + app.open_memory_viewer + Show Memory Viewer + True + + + + + True + False + app.open_vram_viewer + Show VRAM Viewer + True + + + + + True + False + + + + + True + False + app.open_gtk_debugger + Show GTK Debugger + True + + + + + + + + + True + False + _Help + True + + + True + False + + + True + False + app.about + _About + True @@ -1343,15 +1700,10 @@ Maximilian Mader https://github.com/max-m False VRAM Viewer False - - - True False - 3 - 3 3 vertical 6 @@ -1815,5 +2167,8 @@ Maximilian Mader https://github.com/max-m + + + diff --git a/gtk3/sameboy.gresource.xml b/gtk3/sameboy.gresource.xml index e13a3f1..289a671 100644 --- a/gtk3/sameboy.gresource.xml +++ b/gtk3/sameboy.gresource.xml @@ -2,9 +2,11 @@ ui/window.ui + css/main.css gamecontrollerdb_456ad4d.txt