[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.
This commit is contained in:
Maximilian Mader 2020-05-14 22:53:02 +02:00
parent add54953c6
commit 1d7034fb88
Signed by: Max
GPG Key ID: F71D56A3151C4FB3
4 changed files with 128 additions and 272 deletions

View File

@ -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 cant 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 applications menu.
//
// This function tries to stick to the desktop environments 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;
}
}
}
// 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 cant 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);

View File

@ -1206,26 +1206,6 @@ Maximilian Mader https://github.com/max-m</property>
<placeholder/>
</child>
</object>
<object class="GtkHeaderBar" id="main_header_bar">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="has_subtitle">False</property>
<property name="show_close_button">True</property>
<child>
<object class="GtkMenuButton" id="hamburger_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="direction">none</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="pack_type">end</property>
</packing>
</child>
</object>
<object class="GtkWindow" id="memory_viewer">
<property name="can_focus">False</property>
<property name="title" translatable="yes">Memory Viewer</property>
@ -1496,46 +1476,11 @@ Maximilian Mader https://github.com/max-m</property>
</object>
</child>
<child>
<object class="GtkSeparatorMenuItem">
<object class="GtkSeparatorMenuItem" id="before_model_changer">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
</child>
<child>
<object class="GtkRadioMenuItem" id="change_model_dmg">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Game Boy</property>
<property name="use_underline">True</property>
</object>
</child>
<child>
<object class="GtkRadioMenuItem" id="change_model_sgb">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Super Game Boy</property>
<property name="use_underline">True</property>
<property name="group">change_model_dmg</property>
</object>
</child>
<child>
<object class="GtkRadioMenuItem" id="change_model_cgb">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Game Boy Color</property>
<property name="use_underline">True</property>
<property name="group">change_model_dmg</property>
</object>
</child>
<child>
<object class="GtkRadioMenuItem" id="change_model_agb">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Game Boy Advance</property>
<property name="use_underline">True</property>
<property name="group">change_model_dmg</property>
</object>
</child>
<child>
<object class="GtkSeparatorMenuItem">
<property name="visible">True</property>
@ -1562,26 +1507,9 @@ Maximilian Mader https://github.com/max-m</property>
<property name="label" translatable="yes">_Link</property>
<property name="use_underline">True</property>
<child type="submenu">
<object class="GtkMenu">
<object class="GtkMenu" id="link_menu">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkRadioMenuItem" id="change_serial_device_none">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">None</property>
<property name="use_underline">True</property>
</object>
</child>
<child>
<object class="GtkRadioMenuItem" id="change_serial_device_printer">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Game Boy Printer</property>
<property name="use_underline">True</property>
<property name="group">change_serial_device_none</property>
</object>
</child>
</object>
</child>
</object>

View File

@ -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;

View File

@ -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);