SameBoy/gtk3/main.c

265 lines
8.6 KiB
C
Raw Normal View History

2020-04-11 16:24:55 +00:00
#include <gtk/gtk.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <Core/gb.h>
#define str(x) #x
#define xstr(x) str(x)
#define RESOURCE_PREFIX "/io/github/sameboy/"
#define APP_ID "io.github.sameboy"
GtkBuilder *builder;
GtkWindow *main_window;
GtkGLArea *gl_area;
GB_gameboy_t gb;
typedef struct UserData {
bool fullscreen;
GFile *file;
} UserData;
// Determines if a ComboBox entry should be converted into a separator.
// Each element with a text value of `<separator>` will be converted into a separator element.
static gboolean is_separator(GtkTreeModel *model, GtkTreeIter *iter, gpointer data) {
gchar *text = NULL;
gtk_tree_model_get(model, iter, 0, &text, -1);
gboolean result = g_strcmp0("<separator>", text) == 0;
return result;
}
// Recursively goes through all children of the given container and sets
// our `is_separator` function to all children of type`GtkComboBox`
static void set_combo_box_row_separator_func(GtkContainer *container) {
GList *list = gtk_container_get_children(container);
while (list) {
if (GTK_IS_COMBO_BOX(list->data)) {
gtk_combo_box_set_row_separator_func(GTK_COMBO_BOX(list->data), is_separator, NULL, NULL);
}
if (GTK_IS_CONTAINER(list->data)) {
set_combo_box_row_separator_func(GTK_CONTAINER(list->data));
}
list = list->next;
}
g_list_free_full(list, NULL);
}
// Returns true if the application should show a menubar
static gboolean show_menubar(void) {
GtkSettings *settings = gtk_settings_get_default();
gboolean result;
g_object_get(settings, "gtk-shell-shows-menubar", &result, NULL);
return result;
}
// Returns a GObject by ID from our GtkBuilder instance
static GObject *get_object(gchararray id) {
return gtk_builder_get_object(builder, id);
}
// 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;
menu = gtk_application_get_menu_by_id(GTK_APPLICATION(app), id);
return menu ? G_MENU_MODEL(g_object_ref_sink(menu)) : NULL;
}
// app.quit GAction
// Exits the application
static void activate_quit(GSimpleAction *action, GVariant *parameter, gpointer user_data) {
g_application_quit(G_APPLICATION(user_data));
}
// app.about GAction
// Opens the about dialog
static void activate_about(GSimpleAction *action, GVariant *parameter, gpointer user_data) {
GObject *dialog = get_object("about_dialog");
gtk_dialog_run(GTK_DIALOG(dialog));
gtk_widget_hide(GTK_WIDGET(dialog));
}
// List of GActions for the `app` prefix
static GActionEntry app_entries[] = {
{ "quit", activate_quit, NULL, NULL, NULL },
{ "about", activate_about, NULL, NULL, NULL },
};
G_MODULE_EXPORT void on_show_window(GtkWidget *w, gpointer window) {
gtk_widget_show_all(GTK_WIDGET(window));
}
G_MODULE_EXPORT void on_boot_rom_location_changed(GtkWidget *w, gpointer user_data_gptr) {
GtkComboBox *box = GTK_COMBO_BOX(w);
g_print("Active: %s", gtk_combo_box_get_active_id(box));
}
// This functions gets called immediately after registration of the GApplication
static void startup(GApplication *app, gpointer user_data_gptr) {
// UserData *user_data = user_data_gptr;
builder = gtk_builder_new_from_resource(RESOURCE_PREFIX "ui/window.ui");
gtk_builder_connect_signals(builder, NULL);
g_action_map_add_action_entries(G_ACTION_MAP(app), app_entries, G_N_ELEMENTS(app_entries), app);
GtkWindow *preferences = GTK_WINDOW(get_object("preferences"));
set_combo_box_row_separator_func(GTK_CONTAINER(preferences));
GtkWindow *vram_viewer = GTK_WINDOW(get_object("vram_viewer"));
set_combo_box_row_separator_func(GTK_CONTAINER(vram_viewer));
// Handle the whole menubar situation …
if (show_menubar()) {
// Use a standard window as main window
main_window = GTK_WINDOW(get_object("main_no_titlebar"));
gtk_widget_destroy(GTK_WIDGET(get_object("main_with_titlebar")));
// Hide hamburger button
GtkMenuButton *hamburger_button = GTK_MENU_BUTTON(get_object("hamburger_button"));
gtk_widget_hide(GTK_WIDGET(hamburger_button));
gtk_widget_set_no_show_all(GTK_WIDGET(hamburger_button), TRUE);
GMenuModel *menubar = get_menu_model(app, "menubar");
gtk_application_set_menubar(GTK_APPLICATION(app), menubar);
}
else {
// Use a window with a custom title bar
main_window = GTK_WINDOW(get_object("main_with_titlebar"));
gtk_widget_destroy(GTK_WIDGET(get_object("main_no_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"));
GMenuModel *hamburger_menu = get_menu_model(app, "menubar");
gtk_menu_button_set_menu_model(hamburger_button, hamburger_menu);
}
gtk_window_set_title(main_window, "SameBoy v" xstr(VERSION));
// Define a set of window icons
GList *icon_list = NULL;
static char* icons[] = {
RESOURCE_PREFIX "logo_256.png",
RESOURCE_PREFIX "logo_128.png",
RESOURCE_PREFIX "logo_64.png",
RESOURCE_PREFIX "logo_48.png",
RESOURCE_PREFIX "logo_32.png",
RESOURCE_PREFIX "logo_16.png"
};
// Create list of GdkPixbufs
for (int i = 0; i < (sizeof(icons) / sizeof(const char*)); ++i) {
GdkPixbuf *icon = gdk_pixbuf_new_from_resource(icons[i], NULL);
if (!icon) continue;
icon_list = g_list_prepend(icon_list, icon);
}
// Let GTK choose the proper icon
gtk_window_set_icon_list(main_window, icon_list);
// Add missing information to the about dialog
GtkAboutDialog *about_dialog = GTK_ABOUT_DIALOG(get_object("about_dialog"));
gtk_about_dialog_set_logo(about_dialog, g_list_nth_data(icon_list, 3)); // reuse the 64x64 icon
gtk_about_dialog_set_version(about_dialog, "v" xstr(VERSION));
g_list_free(icon_list);
GList *children = gtk_container_get_children(GTK_CONTAINER(main_window));
gl_area = GTK_GL_AREA(g_list_first(children));
g_list_free(children);
}
// This function gets called when the GApplication gets activated, i.e. it is ready to show widgets.
static void activate(GApplication *app, gpointer user_data_gptr) {
UserData *user_data = user_data_gptr;
if (user_data->fullscreen) {
gtk_window_fullscreen(main_window);
}
g_signal_connect(main_window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
gtk_application_add_window(GTK_APPLICATION(app), main_window);
gtk_widget_show_all(GTK_WIDGET(main_window));
}
// This function gets called when there are files to open.
// Note: When `open` gets called `activate` wont fire unless we call it ourselves.
static void open(GApplication *app, GFile **files, gint n_files, const gchar *hint, gpointer user_data_gptr) {
UserData *user_data = user_data_gptr;
if (n_files > 1) {
g_printerr("More than one file specified\n");
g_application_quit(app);
return;
}
user_data->file = files[0];
// We have handled the files, now activate the application
activate(app, user_data_gptr);
}
// This function gets called after the parsing of the commandline options has occurred.
static gint handle_local_options(GApplication *app, GVariantDict *options, gpointer user_data_gptr) {
UserData *user_data = user_data_gptr;
guint32 count;
if (g_variant_dict_lookup(options, "version", "b", &count)) {
g_print("SameBoy v" xstr(VERSION) "\n");
return EXIT_SUCCESS;
}
if (g_variant_dict_lookup(options, "fullscreen", "b", &count)) {
user_data->fullscreen = true;
}
return -1;
}
int main (int argc, char *argv[]) {
// Create our GApplication and tell GTK that we are able to handle files
GtkApplication *app = gtk_application_new(APP_ID, G_APPLICATION_HANDLES_OPEN);
UserData user_data = { NULL };
// Define our command line parameters
GOptionEntry entries[] = {
{ "version", 'v', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, NULL, "Show the application version", NULL },
{ "fullscreen", 'f', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, NULL, "Start in fullscreen mode", NULL },
{ NULL }
};
// Setup our command line information
g_application_add_main_option_entries(G_APPLICATION(app), entries);
g_application_set_option_context_parameter_string(G_APPLICATION(app), "[FILE…]");
g_application_set_option_context_summary(G_APPLICATION(app), "SameBoy is an open source Game Boy (DMG) and Game Boy Color (CGB) emulator.");
// Add signal handlers
g_signal_connect(app, "handle-local-options", G_CALLBACK(handle_local_options), &user_data);
g_signal_connect(app, "startup", G_CALLBACK(startup), &user_data);
g_signal_connect(app, "activate", G_CALLBACK(activate), &user_data);
g_signal_connect(app, "open", G_CALLBACK(open), &user_data);
// Start our GApplication main loop
int status = g_application_run(G_APPLICATION(app), argc, argv);
g_object_unref(app);
return status;
}