[GTK3] Implement the app.close action
This commit is contained in:
parent
e42d16290d
commit
c00946ea2e
@ -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 */
|
||||
|
183
gtk3/main.c
183
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) {
|
||||
|
@ -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);
|
||||
|
@ -229,7 +229,7 @@ Author: Maximilian Mader
|
||||
</submenu>
|
||||
|
||||
<submenu>
|
||||
<attribute name="label" translatable="yes">Save State</attribute>
|
||||
<attribute name="label" translatable="yes">Load State</attribute>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Slot 1</attribute>
|
||||
<attribute name="save-slot">0</attribute>
|
||||
|
Loading…
Reference in New Issue
Block a user