diff options
Diffstat (limited to 'src/libui_sdl/libui/unix/menu.c')
-rw-r--r-- | src/libui_sdl/libui/unix/menu.c | 366 |
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); +} |