mirror of
https://github.com/melonDS-emu/melonDS.git
synced 2025-08-03 11:38:59 -06:00
another UI attempt, I guess.
sorry.
This commit is contained in:
366
src/libui_sdl/libui/unix/menu.c
Normal file
366
src/libui_sdl/libui/unix/menu.c
Normal file
@ -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);
|
||||
}
|
Reference in New Issue
Block a user