[GTK3] Implement the app.close action

This commit is contained in:
Maximilian Mader 2020-04-12 00:38:18 +02:00
parent e42d16290d
commit c00946ea2e
Signed by: Max
GPG Key ID: F71D56A3151C4FB3
4 changed files with 133 additions and 56 deletions

View File

@ -5,6 +5,7 @@
#define xstr(x) str(x) #define xstr(x) str(x)
#define get_object(id) gtk_builder_get_object(builder, id) #define get_object(id) gtk_builder_get_object(builder, id)
#define builder_get(type, id) type(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) #define itoa(val) g_strdup_printf("%i", val)
#endif /* macros_h */ #endif /* macros_h */

View File

@ -86,25 +86,39 @@ static GCond debugger_input_cond;
static GRecMutex console_output_lock; static GRecMutex console_output_lock;
static GPtrArray *debugger_input_queue; static GPtrArray *debugger_input_queue;
// List of GActions for the `app` prefix static const GActionEntry file_entries[] = {
// 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 },
{ "open", activate_open, NULL, NULL, NULL }, { "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 }, { "show_console", activate_show_console, NULL, NULL, NULL },
{ "open_gtk_debugger", activate_open_gtk_debugger, 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 }, { "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 }, { "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 }, { "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[]) { int main(int argc, char *argv[]) {
main_thread = g_thread_self(); main_thread = g_thread_self();
@ -739,7 +753,29 @@ static void flip(void) {
} }
static void quit_interrupt(int ignored) { 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 // 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"); builder = gtk_builder_new_from_resource(RESOURCE_PREFIX "ui/window.ui");
gtk_builder_connect_signals(builder, NULL); gtk_builder_connect_signals(builder, NULL);
// Setup application actions create_action_groups(app);
g_action_map_add_action_entries(G_ACTION_MAP(app), app_entries, G_N_ELEMENTS(app_entries), app);
if (gui_data->cli_options.prefix != NULL) { if (gui_data->cli_options.prefix != NULL) {
GAction *action = g_action_map_lookup_action(G_ACTION_MAP(main_application), "change_model"); 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 #if NDEBUG
// Disable when not compiled in debug mode // 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 // 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_label"));
@ -839,16 +874,7 @@ static void startup(GApplication *app, gpointer gui_data_gptr) {
g_list_free_full(icon_list, g_object_unref); 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 connect_signal_handlers(GApplication *app) {
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 // 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_PRESS_MASK);
gtk_widget_add_events(GTK_WIDGET(main_window), GDK_KEY_RELEASE_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_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); 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 // create our renderer area
if (supports_gl) { if (supports_gl) {
gl_area = GTK_GL_AREA(gtk_gl_area_new()); 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(); GtkCssProvider *provider = gtk_css_provider_new();
gtk_css_provider_load_from_resource(provider, RESOURCE_PREFIX "css/main.css"); 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); 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"); GtkTextView *text_view = builder_get(GTK_TEXT_VIEW, "console_screen");
GtkTextBuffer *text_buf = gtk_text_view_get_buffer(text_view); 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, "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); 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_mutex_init(&debugger_input_mutex);
g_cond_init(&debugger_input_cond); g_cond_init(&debugger_input_cond);
g_rec_mutex_init(&console_output_lock); g_rec_mutex_init(&console_output_lock);
@ -929,6 +952,28 @@ static void activate(GApplication *app, gpointer gui_data_gptr) {
if (!pending_console_output) { if (!pending_console_output) {
pending_console_output = gtk_text_buffer_new(gtk_text_buffer_get_tag_table(text_buf)); 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 // Start the emulation thread
run(gui_data); 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. // 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 // TODO: Make sure we have a way to quit our emulation loop before `shutdown` gets called
static void quit(GApplication *app) { static void quit() {
// Tell our own main loop to quit. stop(&gui_data);
// This will allow our run() and therefore our activate() methods to end.
running = false;
if (app) {
// Quit our application properly. // Quit our application properly.
// This fires the “shutdown” signal. // This fires the “shutdown” signal.
g_application_quit(app); g_application_quit(G_APPLICATION(main_application));
}
} }
static gboolean on_key_press(GtkWidget *w, GdkEventKey *event, gpointer data) { 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 // app.open GAction
// Opens a ROM file // Opens a ROM file
static void activate_open(GSimpleAction *action, GVariant *parameter, gpointer app) { 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"); 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)); 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); activate_reset(action, parameter, app);
} }
else {
run(&gui_data);
}
g_object_unref(native); 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 // app.preferences GAction
// Opens the preferences window // Opens the preferences window
static void activate_preferences(GSimpleAction *action, GVariant *parameter, gpointer app) { 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 // app.quit GAction
// Exits the application // Exits the application
static void activate_quit(GSimpleAction *action, GVariant *parameter, gpointer app) { static void activate_quit(GSimpleAction *action, GVariant *parameter, gpointer app) {
quit(G_APPLICATION(app)); quit();
} }
// app.reset GAction // app.reset GAction
// Resets the emulation // Resets the emulation
static void activate_reset(GSimpleAction *action, GVariant *parameter, gpointer app) { static void activate_reset(GSimpleAction *action, GVariant *parameter, gpointer app) {
if (gui_data.stopped) { if (!GB_is_inited(&gb)) {
reset(&gui_data); init(&gui_data);
} }
else {
stop(&gui_data); stop(&gui_data);
reset(&gui_data); reset(&gui_data);
run(&gui_data); run(&gui_data);
}
} }
// app.clear_console GAction // app.clear_console GAction
@ -1206,7 +1271,7 @@ static void on_pause_changed(GSimpleAction *action, GVariant *value, gpointer us
// `destroy` signal GCallback // `destroy` signal GCallback
// Exits the application // Exits the application
static void on_quit(GtkWidget *w, gpointer app) { static void on_quit(GtkWidget *w, gpointer app) {
quit(G_APPLICATION(app)); quit();
} }
// TODO: Comment // TODO: Comment
@ -2105,6 +2170,7 @@ static gpointer run_thread(gpointer gui_data_gptr) {
return NULL; return NULL;
} }
static void init(GuiData *gui_data) { static void init(GuiData *gui_data) {
if (GB_is_inited(&gb)) return; if (GB_is_inited(&gb)) return;
@ -2234,13 +2300,22 @@ static void reset(GuiData *gui_data) {
update_window_geometry(); update_window_geometry();
} }
bool success = false;
if (gui_data->file) {
char *path = g_file_get_path(gui_data->file); char *path = g_file_get_path(gui_data->file);
if (GB_load_rom(&gb, path) != 0) { if (GB_load_rom(&gb, path) == 0) {
success = true;
}
else {
g_warning("Failed to load ROM: %s", path); 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) { static void start(GuiData *gui_data) {

View File

@ -95,7 +95,7 @@ static void startup(GApplication *app, gpointer user_data_gptr);
static void activate(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 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 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 gboolean on_key_press(GtkWidget *w, GdkEventKey *event, gpointer data);
static void on_window_state_change(GtkWidget *w, GdkEventWindowState *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_memory_viewer(GSimpleAction *action, GVariant *parameter, gpointer app);
static void activate_open_vram_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_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_preferences(GSimpleAction *action, GVariant *parameter, gpointer app);
static void activate_quit(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); static void activate_reset(GSimpleAction *action, GVariant *parameter, gpointer app);

View File

@ -229,7 +229,7 @@ Author: Maximilian Mader
</submenu> </submenu>
<submenu> <submenu>
<attribute name="label" translatable="yes">Save State</attribute> <attribute name="label" translatable="yes">Load State</attribute>
<item> <item>
<attribute name="label" translatable="yes">Slot 1</attribute> <attribute name="label" translatable="yes">Slot 1</attribute>
<attribute name="save-slot">0</attribute> <attribute name="save-slot">0</attribute>