From 1d7034fb88e7d360c1842279b98f048cf872444a Mon Sep 17 00:00:00 2001 From: Maximilian Mader Date: Thu, 14 May 2020 22:53:02 +0200 Subject: [PATCH] [GTK3] Add `GtkRadioMenuItem`s at runtime Defining them in the UI definition file was buggy in Unity and MATE (Mutiny layout). Somehow creating them manually via the API works around that bug. The only problem is that Unity fails to update the marker for the active menu item on the *first* click. It then lags one item update behind, i.e. 1) CGB is active 2) Click on AGB, CGB is still rendered as active 3) Click on any (including AGB) of the options, now AGB is rendered as active Also: The Gnome 3 style hamburger menu has been removed. --- gtk3/main.c | 265 +++++++++++++++++------------------- gtk3/resources/ui/window.ui | 76 +---------- gtk3/settings.c | 47 ------- gtk3/settings.h | 12 +- 4 files changed, 128 insertions(+), 272 deletions(-) diff --git a/gtk3/main.c b/gtk3/main.c index 1b4a61d..bcd01a7 100644 --- a/gtk3/main.c +++ b/gtk3/main.c @@ -46,7 +46,6 @@ typedef struct GuiData { struct CliOptionData { gchar *config_path; gchar *boot_rom_path; - gchar *prefix; gboolean fullscreen; GB_model_t model; gboolean force_software_renderer; @@ -358,8 +357,6 @@ static gint handle_local_options(GApplication *app, GVariantDict *options, gpoin // TODO: Synchronize with GB_model_t (Core/gb.h) if (g_str_has_prefix(model_name, "DMG")) { - gui_data.cli_options.prefix = "DMG"; - if (g_str_has_suffix(model_name, "-B") || g_strcmp0(model_name, "DMG") == 0) { gui_data.cli_options.model = GB_MODEL_DMG_B; } @@ -369,8 +366,6 @@ static gint handle_local_options(GApplication *app, GVariantDict *options, gpoin } } else if (g_str_has_prefix(model_name, "SGB")) { - gui_data.cli_options.prefix = "SGB"; - if (g_str_has_suffix(model_name, "-NTSC") || g_strcmp0(model_name, "SGB") == 0) { gui_data.cli_options.model = GB_MODEL_SGB; } @@ -386,8 +381,6 @@ static gint handle_local_options(GApplication *app, GVariantDict *options, gpoin } } else if (g_str_has_prefix(model_name, "CGB")) { - gui_data.cli_options.prefix = "CGB"; - if (g_str_has_suffix(model_name, "-C")) { gui_data.cli_options.model = GB_MODEL_CGB_C; } @@ -400,13 +393,9 @@ static gint handle_local_options(GApplication *app, GVariantDict *options, gpoin } } else if (g_str_has_prefix(model_name, "AGB")) { - gui_data.cli_options.prefix = "AGB"; - gui_data.cli_options.model = GB_MODEL_AGB; } else { - gui_data.cli_options.prefix = NULL; - g_warning("Unknown model: %s", model_name); exit(EXIT_FAILURE); } @@ -818,17 +807,87 @@ static char *async_console_input(GB_gameboy_t *gb) { GtkWidget *menubar_to_menu(GtkMenuBar *menubar) { GtkWidget *menu = gtk_menu_new(); - g_autoptr(GList) iter = gtk_container_get_children(GTK_CONTAINER (menubar)); + 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); + iter = iter->next; } return menu; } +// Creating these items in the UI defintion files was buggy in some desktop +// 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); + + static const char *const model_names[] = { + "Game Boy", + "Super Game Boy", + "Game Boy Color", + "Game Boy Advance" + }; + + static const char *const model_codes[] = { + "DMG", + "SGB", + "CGB", + "GBA" + }; + + // Find the menu item index of the previous sibling of the new menu items + GtkWidget *before = builder_get(GTK_WIDGET, "before_model_changer"); + GtkContainer *parent = GTK_CONTAINER(gtk_widget_get_parent(before)); + 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)); + } + + static const char *const peripheral_names[] = { + "None", + "Game Boy Printer", + }; + + static const char *const peripheral_codes[] = { + "NONE", + "PRINTER", + }; + + 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); + } + } +} + // Create our application’s menu. // // This function tries to stick to the desktop environment’s conventions. @@ -836,72 +895,10 @@ GtkWidget *menubar_to_menu(GtkMenuBar *menubar) { // 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) { + create_model_menu_items(); + GtkMenuBar *menubar = builder_get(GTK_MENU_BAR, "main_menu"); - enum menubar_type_t menubar_type = get_show_menubar(); - - // Try to use a sane default - if (menubar_type == MENUBAR_AUTO) { - GtkSettings *settings = gtk_settings_get_default(); - gboolean show_in_shell; - g_object_get(settings, "gtk-shell-shows-menubar", &show_in_shell, NULL); - - const gchar *xdg_current_desktop = g_getenv("XDG_CURRENT_DESKTOP"); - const gchar *gdm_session = g_getenv("GDMSESSION"); - const gchar *desktop_session = g_getenv("DESKTOP_SESSION"); - - gchar *desktop = (gchar *)xdg_current_desktop; - if (desktop == NULL || g_str_equal(desktop, "")) desktop = (gchar *)gdm_session; - if (desktop == NULL || g_str_equal(desktop, "")) desktop = (gchar *)desktop_session; - - g_debug("XDG_CURRENT_DESKTOP: %s\nGDMSESSION: %s\nDESKTOP_SESSION: %s\nChosen value: %s\nShow menu in shell: %d", xdg_current_desktop, gdm_session, desktop_session, desktop, show_in_shell); - - if (desktop != NULL && show_in_shell) { - menubar_type = MENUBAR_SHOW_IN_SHELL; - } - else if (desktop != NULL && g_str_match_string("GNOME", desktop, false)) { - if (g_str_match_string("GNOME-Flashback", desktop, false) || g_str_match_string("GNOME-Classic", desktop, false)) { - menubar_type = MENUBAR_SHOW_IN_WINDOW; - } - else if (gdm_session != NULL && (g_str_match_string("gnome-classic", gdm_session, false) || g_str_match_string("gnome-flashback", gdm_session, false))) { - menubar_type = MENUBAR_SHOW_IN_WINDOW; - } - else { - menubar_type = MENUBAR_SHOW_HAMBURGER; - } - } - else { - menubar_type = MENUBAR_SHOW_IN_WINDOW; - } - } - - switch (menubar_type) { - case MENUBAR_AUTO: - g_warning("Unreachable"); - break; - - case MENUBAR_SHOW_IN_SHELL: - case MENUBAR_SHOW_IN_WINDOW: { - g_debug("Showing menu in the window"); - gtk_box_pack_start(GTK_BOX(gui_data.main_window_container), GTK_WIDGET(menubar), false, false, 0); - break; - } - - case MENUBAR_SHOW_HAMBURGER: { - g_debug("Showing hamburger"); - // Attach a custom title bar - GtkWidget *titlebar = builder_get(GTK_WIDGET, "main_header_bar"); - gtk_header_bar_set_title(GTK_HEADER_BAR(titlebar), gtk_window_get_title(GTK_WINDOW(gui_data.main_window))); - gtk_window_set_titlebar(GTK_WINDOW(gui_data.main_window), titlebar); - - // Disable menubar - gtk_application_set_menubar(GTK_APPLICATION(app), NULL); - - // Hook menubar up to the hamburger button - GtkMenuButton *hamburger_button = GTK_MENU_BUTTON(get_object("hamburger_button")); - gtk_menu_button_set_popup(hamburger_button, GTK_WIDGET(menubar_to_menu(menubar))); - break; - } - } + gtk_box_pack_start(GTK_BOX(gui_data.main_window_container), GTK_WIDGET(menubar), false, false, 0); } // Determines if a ComboBox entry should be converted into a separator. @@ -963,6 +960,42 @@ static void flip(void) { gui_data.current_buffer = (gui_data.current_buffer + 1) % number_of_buffers(); } +static void update_viewport(void) { + GtkWidget *w = gui_data.fallback_canvas ? GTK_WIDGET(gui_data.fallback_canvas) : GTK_WIDGET(gui_data.gl_area); + + int win_width = gtk_widget_get_allocated_width(w); + int win_height = gtk_widget_get_allocated_height(w); + + double x_factor = win_width / (double) GB_get_screen_width(&gb); + double y_factor = win_height / (double) GB_get_screen_height(&gb); + + if (config.video.use_integer_scaling) { + x_factor = (int)(x_factor); + y_factor = (int)(y_factor); + } + + if (config.video.keep_aspect_ratio) { + if (x_factor > y_factor) { + x_factor = y_factor; + } + else { + y_factor = x_factor; + } + } + + unsigned new_width = x_factor * GB_get_screen_width(&gb); + unsigned new_height = y_factor * GB_get_screen_height(&gb); + + gui_data.viewport = (Rect){ + (win_width - new_width) / 2, + (win_height - new_height) / 2, + new_width, + new_height + }; + + if (!gui_data.fallback_canvas) glViewport(gui_data.viewport.x, gui_data.viewport.y, gui_data.viewport.w, gui_data.viewport.h); +} + // 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 @@ -978,15 +1011,20 @@ static void update_window_geometry(void) { g_debug("update_window_geometry: %u×%u → %u×%u", gui_data.last_screen_width, gui_data.last_screen_height, GB_get_screen_width(&gb), GB_get_screen_height(&gb)); GtkWidget *w = gui_data.fallback_canvas ? GTK_WIDGET(gui_data.fallback_canvas) : GTK_WIDGET(gui_data.gl_area); - int win_width = gtk_widget_get_allocated_width(w); - int win_height = gtk_widget_get_allocated_height(w); - unsigned new_width = GB_get_screen_width(&gb) * 2; - unsigned new_height = GB_get_screen_height(&gb) * 2; + signed win_width = gtk_widget_get_allocated_width(w); + signed win_height = gtk_widget_get_allocated_height(w); + signed menu_height = gtk_widget_get_allocated_height(builder_get(GTK_WIDGET, "main_menu")); + + unsigned _factor = win_width > win_height ? win_width / GB_get_screen_width(&gb) : win_height / GB_get_screen_height(&gb); + unsigned factor = _factor < 2 ? 2 : _factor; + + unsigned new_width = GB_get_screen_width(&gb) * factor; + unsigned new_height = GB_get_screen_height(&gb) * factor + menu_height; // Set size hints GdkGeometry hints; hints.min_width = GB_get_screen_width(&gb); - hints.min_height = GB_get_screen_height(&gb); + hints.min_height = GB_get_screen_height(&gb) + menu_height; gtk_window_set_geometry_hints( GTK_WINDOW(gui_data.main_window), @@ -1016,6 +1054,8 @@ static void update_window_geometry(void) { if (GB_is_inited(&gb)) { GB_set_pixels_output(&gb, get_pixels()); } + + update_viewport(); } static void stop(void) { @@ -1989,20 +2029,9 @@ static void startup(GApplication *app, gpointer null_ptr) { create_action_groups(app); - if (gui_data.cli_options.prefix != NULL) { - // 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 // Disable when not compiled in debug mode action_set_enabled(app, "open_gtk_debugger", false); - - // Remove the menubar override - gtk_widget_destroy(builder_get(GTK_WIDGET, "menubar_override_selector_label")); - gtk_widget_destroy(builder_get(GTK_WIDGET, "menubar_override_selector")); #endif gui_data.preferences = GTK_WINDOW(get_object("preferences")); @@ -2138,48 +2167,6 @@ 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) { - GtkWidget *w = gui_data.fallback_canvas ? GTK_WIDGET(gui_data.fallback_canvas) : GTK_WIDGET(gui_data.gl_area); - - int win_width = gtk_widget_get_allocated_width(w); - int win_height = gtk_widget_get_allocated_height(w); - - double x_factor = win_width / (double) GB_get_screen_width(&gb); - double y_factor = win_height / (double) GB_get_screen_height(&gb); - - if (config.video.use_integer_scaling) { - x_factor = (int)(x_factor); - y_factor = (int)(y_factor); - } - - if (config.video.keep_aspect_ratio) { - if (x_factor > y_factor) { - x_factor = y_factor; - } - else { - y_factor = x_factor; - } - } - - unsigned new_width = x_factor * GB_get_screen_width(&gb); - unsigned new_height = y_factor * GB_get_screen_height(&gb); - - gui_data.viewport = (Rect){ - (win_width - new_width) / 2, - (win_height - new_height) / 2, - new_width, - new_height - }; - - if (!gui_data.fallback_canvas) glViewport(gui_data.viewport.x, gui_data.viewport.y, gui_data.viewport.w, gui_data.viewport.h); } // TODO: Comment @@ -2362,6 +2349,8 @@ static void activate(GApplication *app, gpointer null_ptr) { gtk_application_add_window(GTK_APPLICATION(app), GTK_WINDOW(gui_data.main_window)); gtk_widget_show_all(GTK_WIDGET(gui_data.main_window)); + update_window_geometry(); + // Start the emulation thread run(); } @@ -2623,10 +2612,6 @@ G_MODULE_EXPORT void on_monochrome_palette_changed(GtkWidget *w, gpointer user_d GB_set_palette(&gb, get_monochrome_palette()); } -G_MODULE_EXPORT void on_color_menubar_override_changed(GtkWidget *w, gpointer user_data_ptr) { - config.window.menubar_override = (gchar *)gtk_combo_box_get_active_id(GTK_COMBO_BOX(w)); -} - G_MODULE_EXPORT void on_dmg_model_changed(GtkWidget *w, gpointer user_data_ptr) { GtkComboBox *box = GTK_COMBO_BOX(w); config.emulation.dmg_revision_name = (gchar *)gtk_combo_box_get_active_id(box); diff --git a/gtk3/resources/ui/window.ui b/gtk3/resources/ui/window.ui index 645ed51..b275a65 100644 --- a/gtk3/resources/ui/window.ui +++ b/gtk3/resources/ui/window.ui @@ -1206,26 +1206,6 @@ Maximilian Mader https://github.com/max-m - - True - False - False - True - - - True - True - True - none - - - - - - end - - - False Memory Viewer @@ -1496,46 +1476,11 @@ Maximilian Mader https://github.com/max-m - + 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 @@ -1562,26 +1507,9 @@ Maximilian Mader https://github.com/max-m _Link True - + True False - - - True - False - None - True - - - - - True - False - Game Boy Printer - True - change_serial_device_none - - diff --git a/gtk3/settings.c b/gtk3/settings.c index c399b45..639a1cc 100644 --- a/gtk3/settings.c +++ b/gtk3/settings.c @@ -146,15 +146,6 @@ void on_preferences_realize(GtkWidget *w, gpointer builder_ptr) { gtk_toggle_button_set_active(builder_get(GTK_TOGGLE_BUTTON, "analog_speed_controls_toggle"), config.controls.analog_speed_controls); gtk_combo_box_set_active_id(builder_get(GTK_COMBO_BOX, "rumble_mode_selector"), config.controls.rumble_mode); - - #if ! NDEBUG - gtk_combo_box_set_active_id(builder_get(GTK_COMBO_BOX, "menubar_override_selector"), config.window.menubar_override); - #else - if (builder_get(GTK_COMBO_BOX, "menubar_override_selector") != NULL) { - gtk_widget_destroy(GTK_WIDGET(builder_get(GTK_COMBO_BOX, "menubar_override_selector"))); - gtk_widget_destroy(GTK_WIDGET(builder_get(GTK_COMBO_BOX, "menubar_override_selector_label"))); - } - #endif } void init_settings(GApplication *app, gchar *path, GDateTime **modification_date, GtkWindow *preferences) { @@ -259,44 +250,6 @@ void update_boot_rom_selector(GtkBuilder *builder) { gtk_combo_box_text_append(combo_box, "other", "Other"); } -enum menubar_type_t get_show_menubar(void) { - if (config.window.menubar_override == NULL) goto default_value; - - if (g_strcmp0(config.window.menubar_override, "auto") == 0) { - return MENUBAR_AUTO; - } - else if (g_strcmp0(config.window.menubar_override, "show_in_shell") == 0) { - return MENUBAR_SHOW_IN_SHELL; - } - else if (g_strcmp0(config.window.menubar_override, "show_in_window") == 0) { - return MENUBAR_SHOW_IN_WINDOW; - } - else if (g_strcmp0(config.window.menubar_override, "show_hamburger") == 0) { - return MENUBAR_SHOW_HAMBURGER; - } - - // This should not happen - g_warning("Unknown menubar setting: %s\nFalling back to “Auto”", config.window.menubar_override); - default_value: return MENUBAR_AUTO; -} - -void set_show_menubar(enum menubar_type_t value) { - switch (value) { - case MENUBAR_AUTO: - config.window.menubar_override = "auto"; - break; - case MENUBAR_SHOW_IN_SHELL: - config.window.menubar_override = "show_in_shell"; - break; - case MENUBAR_SHOW_IN_WINDOW: - config.window.menubar_override = "show_in_window"; - break; - case MENUBAR_SHOW_HAMBURGER: - config.window.menubar_override = "show_hamburger"; - break; - } -} - GB_color_correction_mode_t get_color_correction_mode(void) { if (config.video.color_correction_id == NULL) goto default_value; diff --git a/gtk3/settings.h b/gtk3/settings.h index 0caf1a4..dcea76c 100644 --- a/gtk3/settings.h +++ b/gtk3/settings.h @@ -54,7 +54,7 @@ EXPAND_GROUP_MEMBER(rumble_mode, string, "Never") \ ) \ EXPAND_GROUP(window, \ - EXPAND_GROUP_MEMBER(menubar_override, string, "auto") \ + \ ) typedef struct config_t { @@ -69,13 +69,6 @@ typedef struct config_t { #undef EXPAND_GROUP_MEMBER } config_t; -enum menubar_type_t { - MENUBAR_AUTO, - MENUBAR_SHOW_IN_SHELL, - MENUBAR_SHOW_IN_WINDOW, - MENUBAR_SHOW_HAMBURGER -}; - config_t config; void on_preferences_realize(GtkWidget *w, gpointer builder_ptr); @@ -90,9 +83,6 @@ void free_settings(void); void update_boot_rom_selector(GtkBuilder *builder); -enum menubar_type_t get_show_menubar(void); -void set_show_menubar(enum menubar_type_t); - GB_color_correction_mode_t get_color_correction_mode(void); void set_color_correction_mode(GB_color_correction_mode_t);