aboutsummaryrefslogtreecommitdiff
path: root/src/libui_sdl/libui/unix/menu.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libui_sdl/libui/unix/menu.c')
-rw-r--r--src/libui_sdl/libui/unix/menu.c366
1 files changed, 366 insertions, 0 deletions
diff --git a/src/libui_sdl/libui/unix/menu.c b/src/libui_sdl/libui/unix/menu.c
new file mode 100644
index 0000000..5ccb4a5
--- /dev/null
+++ b/src/libui_sdl/libui/unix/menu.c
@@ -0,0 +1,366 @@
+// 23 april 2015
+#include "uipriv_unix.h"
+
+static GArray *menus = NULL;
+static gboolean menusFinalized = FALSE;
+static gboolean hasQuit = FALSE;
+static gboolean hasPreferences = FALSE;
+static gboolean hasAbout = FALSE;
+
+struct uiMenu {
+ char *name;
+ GArray *items; // []*uiMenuItem
+};
+
+struct uiMenuItem {
+ char *name;
+ int type;
+ void (*onClicked)(uiMenuItem *, uiWindow *, void *);
+ void *onClickedData;
+ GType gtype; // template for new instances; kept in sync with everything else
+ gboolean disabled;
+ gboolean checked;
+ GHashTable *windows; // map[GtkMenuItem]*menuItemWindow
+};
+
+struct menuItemWindow {
+ uiWindow *w;
+ gulong signal;
+};
+
+enum {
+ typeRegular,
+ typeCheckbox,
+ typeQuit,
+ typePreferences,
+ typeAbout,
+ typeSeparator,
+};
+
+// we do NOT want programmatic updates to raise an ::activated signal
+static void singleSetChecked(GtkCheckMenuItem *menuitem, gboolean checked, gulong signal)
+{
+ g_signal_handler_block(menuitem, signal);
+ gtk_check_menu_item_set_active(menuitem, checked);
+ g_signal_handler_unblock(menuitem, signal);
+}
+
+static void setChecked(uiMenuItem *item, gboolean checked)
+{
+ GHashTableIter iter;
+ gpointer widget;
+ gpointer ww;
+ struct menuItemWindow *w;
+
+ item->checked = checked;
+ g_hash_table_iter_init(&iter, item->windows);
+ while (g_hash_table_iter_next(&iter, &widget, &ww)) {
+ w = (struct menuItemWindow *) ww;
+ singleSetChecked(GTK_CHECK_MENU_ITEM(widget), item->checked, w->signal);
+ }
+}
+
+static void onClicked(GtkMenuItem *menuitem, gpointer data)
+{
+ uiMenuItem *item = uiMenuItem(data);
+ struct menuItemWindow *w;
+
+ // we need to manually update the checked states of all menu items if one changes
+ // notice that this is getting the checked state of the menu item that this signal is sent from
+ if (item->type == typeCheckbox)
+ setChecked(item, gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(menuitem)));
+
+ w = (struct menuItemWindow *) g_hash_table_lookup(item->windows, menuitem);
+ (*(item->onClicked))(item, w->w, item->onClickedData);
+}
+
+static void defaultOnClicked(uiMenuItem *item, uiWindow *w, void *data)
+{
+ // do nothing
+}
+
+static void onQuitClicked(uiMenuItem *item, uiWindow *w, void *data)
+{
+ if (shouldQuit())
+ uiQuit();
+}
+
+static void menuItemEnableDisable(uiMenuItem *item, gboolean enabled)
+{
+ GHashTableIter iter;
+ gpointer widget;
+
+ item->disabled = !enabled;
+ g_hash_table_iter_init(&iter, item->windows);
+ while (g_hash_table_iter_next(&iter, &widget, NULL))
+ gtk_widget_set_sensitive(GTK_WIDGET(widget), enabled);
+}
+
+void uiMenuItemEnable(uiMenuItem *item)
+{
+ menuItemEnableDisable(item, TRUE);
+}
+
+void uiMenuItemDisable(uiMenuItem *item)
+{
+ menuItemEnableDisable(item, FALSE);
+}
+
+void uiMenuItemOnClicked(uiMenuItem *item, void (*f)(uiMenuItem *, uiWindow *, void *), void *data)
+{
+ if (item->type == typeQuit)
+ userbug("You cannot call uiMenuItemOnClicked() on a Quit item; use uiOnShouldQuit() instead.");
+ item->onClicked = f;
+ item->onClickedData = data;
+}
+
+int uiMenuItemChecked(uiMenuItem *item)
+{
+ return item->checked != FALSE;
+}
+
+void uiMenuItemSetChecked(uiMenuItem *item, int checked)
+{
+ gboolean c;
+
+ // use explicit values
+ c = FALSE;
+ if (checked)
+ c = TRUE;
+ setChecked(item, c);
+}
+
+static uiMenuItem *newItem(uiMenu *m, int type, const char *name)
+{
+ uiMenuItem *item;
+
+ if (menusFinalized)
+ userbug("You cannot create a new menu item after menus have been finalized.");
+
+ item = uiNew(uiMenuItem);
+
+ g_array_append_val(m->items, item);
+
+ item->type = type;
+ switch (item->type) {
+ case typeQuit:
+ item->name = g_strdup("Quit");
+ break;
+ case typePreferences:
+ item->name = g_strdup("Preferences...");
+ break;
+ case typeAbout:
+ item->name = g_strdup("About");
+ break;
+ case typeSeparator:
+ break;
+ default:
+ item->name = g_strdup(name);
+ break;
+ }
+
+ if (item->type == typeQuit) {
+ // can't call uiMenuItemOnClicked() here
+ item->onClicked = onQuitClicked;
+ item->onClickedData = NULL;
+ } else
+ uiMenuItemOnClicked(item, defaultOnClicked, NULL);
+
+ switch (item->type) {
+ case typeCheckbox:
+ item->gtype = GTK_TYPE_CHECK_MENU_ITEM;
+ break;
+ case typeSeparator:
+ item->gtype = GTK_TYPE_SEPARATOR_MENU_ITEM;
+ break;
+ default:
+ item->gtype = GTK_TYPE_MENU_ITEM;
+ break;
+ }
+
+ item->windows = g_hash_table_new(g_direct_hash, g_direct_equal);
+
+ return item;
+}
+
+uiMenuItem *uiMenuAppendItem(uiMenu *m, const char *name)
+{
+ return newItem(m, typeRegular, name);
+}
+
+uiMenuItem *uiMenuAppendCheckItem(uiMenu *m, const char *name)
+{
+ return newItem(m, typeCheckbox, name);
+}
+
+uiMenuItem *uiMenuAppendQuitItem(uiMenu *m)
+{
+ if (hasQuit)
+ userbug("You cannot have multiple Quit menu items in the same program.");
+ hasQuit = TRUE;
+ newItem(m, typeSeparator, NULL);
+ return newItem(m, typeQuit, NULL);
+}
+
+uiMenuItem *uiMenuAppendPreferencesItem(uiMenu *m)
+{
+ if (hasPreferences)
+ userbug("You cannot have multiple Preferences menu items in the same program.");
+ hasPreferences = TRUE;
+ newItem(m, typeSeparator, NULL);
+ return newItem(m, typePreferences, NULL);
+}
+
+uiMenuItem *uiMenuAppendAboutItem(uiMenu *m)
+{
+ if (hasAbout)
+ userbug("You cannot have multiple About menu items in the same program.");
+ hasAbout = TRUE;
+ newItem(m, typeSeparator, NULL);
+ return newItem(m, typeAbout, NULL);
+}
+
+void uiMenuAppendSeparator(uiMenu *m)
+{
+ newItem(m, typeSeparator, NULL);
+}
+
+uiMenu *uiNewMenu(const char *name)
+{
+ uiMenu *m;
+
+ if (menusFinalized)
+ userbug("You cannot create a new menu after menus have been finalized.");
+ if (menus == NULL)
+ menus = g_array_new(FALSE, TRUE, sizeof (uiMenu *));
+
+ m = uiNew(uiMenu);
+
+ g_array_append_val(menus, m);
+
+ m->name = g_strdup(name);
+ m->items = g_array_new(FALSE, TRUE, sizeof (uiMenuItem *));
+
+ return m;
+}
+
+static void appendMenuItem(GtkMenuShell *submenu, uiMenuItem *item, uiWindow *w)
+{
+ GtkWidget *menuitem;
+ gulong signal;
+ struct menuItemWindow *ww;
+
+ menuitem = g_object_new(item->gtype, NULL);
+ if (item->name != NULL)
+ gtk_menu_item_set_label(GTK_MENU_ITEM(menuitem), item->name);
+ if (item->type != typeSeparator) {
+ signal = g_signal_connect(menuitem, "activate", G_CALLBACK(onClicked), item);
+ gtk_widget_set_sensitive(menuitem, !item->disabled);
+ if (item->type == typeCheckbox)
+ singleSetChecked(GTK_CHECK_MENU_ITEM(menuitem), item->checked, signal);
+ }
+ gtk_menu_shell_append(submenu, menuitem);
+ ww = uiNew(struct menuItemWindow);
+ ww->w = w;
+ ww->signal = signal;
+ g_hash_table_insert(item->windows, menuitem, ww);
+}
+
+GtkWidget *makeMenubar(uiWindow *w)
+{
+ GtkWidget *menubar;
+ guint i, j;
+ uiMenu *m;
+ GtkWidget *menuitem;
+ GtkWidget *submenu;
+
+ menusFinalized = TRUE;
+
+ menubar = gtk_menu_bar_new();
+
+ if (menus != NULL)
+ for (i = 0; i < menus->len; i++) {
+ m = g_array_index(menus, uiMenu *, i);
+ menuitem = gtk_menu_item_new_with_label(m->name);
+ submenu = gtk_menu_new();
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu);
+ for (j = 0; j < m->items->len; j++)
+ appendMenuItem(GTK_MENU_SHELL(submenu), g_array_index(m->items, uiMenuItem *, j), w);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menubar), menuitem);
+ }
+
+ gtk_widget_set_hexpand(menubar, TRUE);
+ gtk_widget_set_halign(menubar, GTK_ALIGN_FILL);
+ return menubar;
+}
+
+struct freeMenuItemData {
+ GArray *items;
+ guint i;
+};
+
+static void freeMenuItem(GtkWidget *widget, gpointer data)
+{
+ struct freeMenuItemData *fmi = (struct freeMenuItemData *) data;
+ uiMenuItem *item;
+ struct menuItemWindow *w;
+
+ item = g_array_index(fmi->items, uiMenuItem *, fmi->i);
+ w = (struct menuItemWindow *) g_hash_table_lookup(item->windows, widget);
+ if (g_hash_table_remove(item->windows, widget) == FALSE)
+ implbug("GtkMenuItem %p not in menu item's item/window map", widget);
+ uiFree(w);
+ fmi->i++;
+}
+
+static void freeMenu(GtkWidget *widget, gpointer data)
+{
+ guint *i = (guint *) data;
+ uiMenu *m;
+ GtkMenuItem *item;
+ GtkWidget *submenu;
+ struct freeMenuItemData fmi;
+
+ m = g_array_index(menus, uiMenu *, *i);
+ item = GTK_MENU_ITEM(widget);
+ submenu = gtk_menu_item_get_submenu(item);
+ fmi.items = m->items;
+ fmi.i = 0;
+ gtk_container_foreach(GTK_CONTAINER(submenu), freeMenuItem, &fmi);
+ (*i)++;
+}
+
+void freeMenubar(GtkWidget *mb)
+{
+ guint i;
+
+ i = 0;
+ gtk_container_foreach(GTK_CONTAINER(mb), freeMenu, &i);
+ // no need to worry about destroying any widgets; destruction of the window they're in will do it for us
+}
+
+void uninitMenus(void)
+{
+ uiMenu *m;
+ uiMenuItem *item;
+ guint i, j;
+
+ if (menus == NULL)
+ return;
+ for (i = 0; i < menus->len; i++) {
+ m = g_array_index(menus, uiMenu *, i);
+ g_free(m->name);
+ for (j = 0; j < m->items->len; j++) {
+ item = g_array_index(m->items, uiMenuItem *, j);
+ if (g_hash_table_size(item->windows) != 0)
+ // TODO is this really a userbug()?
+ implbug("menu item %p (%s) still has uiWindows attached; did you forget to destroy some windows?", item, item->name);
+ g_free(item->name);
+ g_hash_table_destroy(item->windows);
+ uiFree(item);
+ }
+ g_array_free(m->items, TRUE);
+ uiFree(m);
+ }
+ g_array_free(menus, TRUE);
+}