SameBoy/gtk3/main.c

265 lines
8.6 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#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;
}