[melonDS] add support for submenus

This commit is contained in:
StapleButter 2017-10-30 03:11:45 +01:00 committed by Mike Sinkovsky
parent 5bd78c1932
commit 476ce2e93a
3 changed files with 129 additions and 17 deletions

1
ui.h
View File

@ -286,6 +286,7 @@ _UI_EXTERN uiMenuItem *uiMenuAppendCheckItem(uiMenu *m, const char *name);
_UI_EXTERN uiMenuItem *uiMenuAppendQuitItem(uiMenu *m); _UI_EXTERN uiMenuItem *uiMenuAppendQuitItem(uiMenu *m);
_UI_EXTERN uiMenuItem *uiMenuAppendPreferencesItem(uiMenu *m); _UI_EXTERN uiMenuItem *uiMenuAppendPreferencesItem(uiMenu *m);
_UI_EXTERN uiMenuItem *uiMenuAppendAboutItem(uiMenu *m); _UI_EXTERN uiMenuItem *uiMenuAppendAboutItem(uiMenu *m);
_UI_EXTERN uiMenuItem *uiMenuAppendSubmenu(uiMenu *m, uiMenu* child);
_UI_EXTERN void uiMenuAppendSeparator(uiMenu *m); _UI_EXTERN void uiMenuAppendSeparator(uiMenu *m);
_UI_EXTERN uiMenu *uiNewMenu(const char *name); _UI_EXTERN uiMenu *uiNewMenu(const char *name);

View File

@ -2,6 +2,7 @@
#include "uipriv_unix.h" #include "uipriv_unix.h"
static GArray *menus = NULL; static GArray *menus = NULL;
static guint nmenus = 0;
static gboolean menusFinalized = FALSE; static gboolean menusFinalized = FALSE;
static gboolean hasQuit = FALSE; static gboolean hasQuit = FALSE;
static gboolean hasPreferences = FALSE; static gboolean hasPreferences = FALSE;
@ -10,6 +11,8 @@ static gboolean hasAbout = FALSE;
struct uiMenu { struct uiMenu {
char *name; char *name;
GArray *items; // []*uiMenuItem GArray *items; // []*uiMenuItem
gboolean ischild;
guint id;
}; };
struct uiMenuItem { struct uiMenuItem {
@ -21,6 +24,7 @@ struct uiMenuItem {
gboolean disabled; gboolean disabled;
gboolean checked; gboolean checked;
GHashTable *windows; // map[GtkMenuItem]*menuItemWindow GHashTable *windows; // map[GtkMenuItem]*menuItemWindow
uiMenu *popupchild;
}; };
struct menuItemWindow { struct menuItemWindow {
@ -35,6 +39,7 @@ enum {
typePreferences, typePreferences,
typeAbout, typeAbout,
typeSeparator, typeSeparator,
typeSubmenu,
}; };
// we do NOT want programmatic updates to raise an ::activated signal // we do NOT want programmatic updates to raise an ::activated signal
@ -179,6 +184,33 @@ static uiMenuItem *newItem(uiMenu *m, int type, const char *name)
} }
item->windows = g_hash_table_new(g_direct_hash, g_direct_equal); item->windows = g_hash_table_new(g_direct_hash, g_direct_equal);
item->popupchild = NULL;
return item;
}
uiMenuItem *uiMenuAppendSubmenu(uiMenu *m, uiMenu* child)
{
uiMenuItem *item;
if (menusFinalized)
uiprivUserBug("You cannot create a new menu item after menus have been finalized.");
item = uiprivNew(uiMenuItem);
g_array_append_val(m->items, item);
item->type = typeSubmenu;
item->name = child->name;
uiMenuItemOnClicked(item, defaultOnClicked, NULL);
// checkme
item->gtype = GTK_TYPE_MENU_ITEM;
item->windows = g_hash_table_new(g_direct_hash, g_direct_equal);
item->popupchild = child;
child->ischild = TRUE;
return item; return item;
} }
@ -237,9 +269,12 @@ uiMenu *uiNewMenu(const char *name)
m = uiprivNew(uiMenu); m = uiprivNew(uiMenu);
g_array_append_val(menus, m); g_array_append_val(menus, m);
m->id = nmenus;
nmenus++;
m->name = g_strdup(name); m->name = g_strdup(name);
m->items = g_array_new(FALSE, TRUE, sizeof (uiMenuItem *)); m->items = g_array_new(FALSE, TRUE, sizeof (uiMenuItem *));
m->ischild = FALSE;
return m; return m;
} }
@ -264,6 +299,18 @@ static void appendMenuItem(GtkMenuShell *submenu, uiMenuItem *item, uiWindow *w)
ww->w = w; ww->w = w;
ww->signal = signal; ww->signal = signal;
g_hash_table_insert(item->windows, menuitem, ww); g_hash_table_insert(item->windows, menuitem, ww);
if (item->popupchild != NULL) {
int j;
uiMenu* m;
GtkWidget *submenu;
m = item->popupchild;
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);
}
} }
GtkWidget *uiprivMakeMenubar(uiWindow *w) GtkWidget *uiprivMakeMenubar(uiWindow *w)
@ -281,6 +328,7 @@ GtkWidget *uiprivMakeMenubar(uiWindow *w)
if (menus != NULL) if (menus != NULL)
for (i = 0; i < menus->len; i++) { for (i = 0; i < menus->len; i++) {
m = g_array_index(menus, uiMenu *, i); m = g_array_index(menus, uiMenu *, i);
if (m->ischild) continue;
menuitem = gtk_menu_item_new_with_label(m->name); menuitem = gtk_menu_item_new_with_label(m->name);
submenu = gtk_menu_new(); submenu = gtk_menu_new();
gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu); gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu);
@ -299,6 +347,8 @@ struct freeMenuItemData {
guint i; guint i;
}; };
static void freeMenu(GtkWidget *widget, gpointer data);
static void freeMenuItem(GtkWidget *widget, gpointer data) static void freeMenuItem(GtkWidget *widget, gpointer data)
{ {
struct freeMenuItemData *fmi = (struct freeMenuItemData *) data; struct freeMenuItemData *fmi = (struct freeMenuItemData *) data;
@ -306,6 +356,8 @@ static void freeMenuItem(GtkWidget *widget, gpointer data)
struct menuItemWindow *w; struct menuItemWindow *w;
item = g_array_index(fmi->items, uiMenuItem *, fmi->i); item = g_array_index(fmi->items, uiMenuItem *, fmi->i);
if (item->popupchild != NULL)
freeMenu(widget, &item->popupchild->id);
w = (struct menuItemWindow *) g_hash_table_lookup(item->windows, widget); w = (struct menuItemWindow *) g_hash_table_lookup(item->windows, widget);
if (g_hash_table_remove(item->windows, widget) == FALSE) if (g_hash_table_remove(item->windows, widget) == FALSE)
uiprivImplBug("GtkMenuItem %p not in menu item's item/window map", widget); uiprivImplBug("GtkMenuItem %p not in menu item's item/window map", widget);
@ -339,28 +391,38 @@ void uiprivFreeMenubar(GtkWidget *mb)
// no need to worry about destroying any widgets; destruction of the window they're in will do it for us // no need to worry about destroying any widgets; destruction of the window they're in will do it for us
} }
void _freeMenu(uiMenu* m)
{
uiMenuItem *item;
guint j;
g_free(m->name);
for (j = 0; j < m->items->len; j++) {
item = g_array_index(m->items, uiMenuItem *, j);
if (item->popupchild != NULL)
_freeMenu(item->popupchild);
if (g_hash_table_size(item->windows) != 0)
// TODO is this really a userbug()?
uiprivImplBug("menu item %p (%s) still has uiWindows attached; did you forget to destroy some windows?", item, item->name);
if (item->type != typeSubmenu) g_free(item->name);
g_hash_table_destroy(item->windows);
uiprivFree(item);
}
g_array_free(m->items, TRUE);
uiprivFree(m);
}
void uiprivUninitMenus(void) void uiprivUninitMenus(void)
{ {
uiMenu *m; uiMenu *m;
uiMenuItem *item; guint i;
guint i, j;
if (menus == NULL) if (menus == NULL)
return; return;
for (i = 0; i < menus->len; i++) { for (i = 0; i < menus->len; i++) {
m = g_array_index(menus, uiMenu *, i); m = g_array_index(menus, uiMenu *, i);
g_free(m->name); if (m->ischild) continue;
for (j = 0; j < m->items->len; j++) { _freeMenu(m);
item = g_array_index(m->items, uiMenuItem *, j);
if (g_hash_table_size(item->windows) != 0)
// TODO is this really a uiprivUserBug()?
uiprivImplBug("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);
uiprivFree(item);
}
g_array_free(m->items, TRUE);
uiprivFree(m);
} }
g_array_free(menus, TRUE); g_array_free(menus, TRUE);
} }

View File

@ -14,7 +14,9 @@ static BOOL hasAbout = FALSE;
struct uiMenu { struct uiMenu {
WCHAR *name; WCHAR *name;
HMENU handle;
uiMenuItem **items; uiMenuItem **items;
BOOL ischild;
size_t len; size_t len;
size_t cap; size_t cap;
}; };
@ -28,6 +30,7 @@ struct uiMenuItem {
BOOL disabled; // template for new instances; kept in sync with everything else BOOL disabled; // template for new instances; kept in sync with everything else
BOOL checked; BOOL checked;
HMENU *hmenus; HMENU *hmenus;
uiMenu* popupchild;
size_t len; size_t len;
size_t cap; size_t cap;
}; };
@ -39,6 +42,7 @@ enum {
typePreferences, typePreferences,
typeAbout, typeAbout,
typeSeparator, typeSeparator,
typeSubmenu,
}; };
#define grow 32 #define grow 32
@ -140,6 +144,7 @@ static uiMenuItem *newItem(uiMenu *m, int type, const char *name)
item->name = toUTF16(name); item->name = toUTF16(name);
break; break;
} }
item->popupchild = NULL;
if (item->type != typeSeparator) { if (item->type != typeSeparator) {
item->id = curID; item->id = curID;
@ -156,6 +161,34 @@ static uiMenuItem *newItem(uiMenu *m, int type, const char *name)
return item; return item;
} }
uiMenuItem *uiMenuAppendSubmenu(uiMenu *m, uiMenu* child)
{
uiMenuItem *item;
if (menusFinalized)
uiprivUserBug("You can not create a new menu item after menus have been finalized.");
if (m->len >= m->cap) {
m->cap += grow;
m->items = (uiMenuItem **) uiprivRealloc(m->items, m->cap * sizeof (uiMenuItem *), "uiMenuitem *[]");
}
item = uiprivNew(uiMenuItem);
m->items[m->len] = item;
m->len++;
item->type = typeSubmenu;
item->name = child->name;
item->popupchild = child;
child->ischild = TRUE;
uiMenuItemOnClicked(item, defaultOnClicked, NULL);
return item;
}
uiMenuItem *uiMenuAppendItem(uiMenu *m, const char *name) uiMenuItem *uiMenuAppendItem(uiMenu *m, const char *name)
{ {
return newItem(m, typeRegular, name); return newItem(m, typeRegular, name);
@ -216,13 +249,17 @@ uiMenu *uiNewMenu(const char *name)
len++; len++;
m->name = toUTF16(name); m->name = toUTF16(name);
m->ischild = FALSE;
return m; return m;
} }
static HMENU makeMenu(uiMenu *m);
static void appendMenuItem(HMENU menu, uiMenuItem *item) static void appendMenuItem(HMENU menu, uiMenuItem *item)
{ {
UINT uFlags; UINT uFlags;
UINT_PTR id = item->id;
uFlags = MF_SEPARATOR; uFlags = MF_SEPARATOR;
if (item->type != typeSeparator) { if (item->type != typeSeparator) {
@ -231,8 +268,12 @@ static void appendMenuItem(HMENU menu, uiMenuItem *item)
uFlags |= MF_DISABLED | MF_GRAYED; uFlags |= MF_DISABLED | MF_GRAYED;
if (item->checked) if (item->checked)
uFlags |= MF_CHECKED; uFlags |= MF_CHECKED;
if (item->popupchild) {
uFlags |= MF_POPUP;
id = (UINT_PTR)makeMenu(item->popupchild);
}
} }
if (AppendMenuW(menu, uFlags, item->id, item->name) == 0) if (AppendMenuW(menu, uFlags, id, item->name) == 0)
logLastError(L"error appending menu item"); logLastError(L"error appending menu item");
if (item->len >= item->cap) { if (item->len >= item->cap) {
@ -251,6 +292,7 @@ static HMENU makeMenu(uiMenu *m)
menu = CreatePopupMenu(); menu = CreatePopupMenu();
if (menu == NULL) if (menu == NULL)
logLastError(L"error creating menu"); logLastError(L"error creating menu");
m->handle = menu;
for (i = 0; i < m->len; i++) for (i = 0; i < m->len; i++)
appendMenuItem(menu, m->items[i]); appendMenuItem(menu, m->items[i]);
return menu; return menu;
@ -269,6 +311,7 @@ HMENU makeMenubar(void)
logLastError(L"error creating menubar"); logLastError(L"error creating menubar");
for (i = 0; i < len; i++) { for (i = 0; i < len; i++) {
if (menus[i]->ischild) continue;
menu = makeMenu(menus[i]); menu = makeMenu(menus[i]);
if (AppendMenuW(menubar, MF_POPUP | MF_STRING, (UINT_PTR) menu, menus[i]->name) == 0) if (AppendMenuW(menubar, MF_POPUP | MF_STRING, (UINT_PTR) menu, menus[i]->name) == 0)
logLastError(L"error appending menu to menubar"); logLastError(L"error appending menu to menubar");
@ -321,21 +364,27 @@ static void freeMenu(uiMenu *m, HMENU submenu)
item->hmenus[j] = item->hmenus[j + 1]; item->hmenus[j] = item->hmenus[j + 1];
item->hmenus[j] = NULL; item->hmenus[j] = NULL;
item->len--; item->len--;
if (item->popupchild)
freeMenu(item->popupchild, item->popupchild->handle);
} }
} }
void freeMenubar(HMENU menubar) void freeMenubar(HMENU menubar)
{ {
size_t i; size_t i;
size_t j = 0;
MENUITEMINFOW mi; MENUITEMINFOW mi;
for (i = 0; i < len; i++) { for (i = 0; i < len; i++) {
if (menus[i]->ischild) continue;
ZeroMemory(&mi, sizeof (MENUITEMINFOW)); ZeroMemory(&mi, sizeof (MENUITEMINFOW));
mi.cbSize = sizeof (MENUITEMINFOW); mi.cbSize = sizeof (MENUITEMINFOW);
mi.fMask = MIIM_SUBMENU; mi.fMask = MIIM_SUBMENU;
if (GetMenuItemInfoW(menubar, i, TRUE, &mi) == 0) if (GetMenuItemInfoW(menubar, j, TRUE, &mi) == 0)
logLastError(L"error getting menu to delete item references from"); logLastError(L"error getting menu to delete item references from");
freeMenu(menus[i], mi.hSubMenu); freeMenu(menus[i], mi.hSubMenu);
j++;
} }
// no need to worry about destroying any menus; destruction of the window they're in will do it for us // no need to worry about destroying any menus; destruction of the window they're in will do it for us
} }
@ -354,7 +403,7 @@ void uninitMenus(void)
if (item->len != 0) if (item->len != 0)
// LONGTERM uiprivUserBug()? // LONGTERM uiprivUserBug()?
uiprivImplBug("menu item %p (%ws) still has uiWindows attached; did you forget to destroy some windows?", item, item->name); uiprivImplBug("menu item %p (%ws) still has uiWindows attached; did you forget to destroy some windows?", item, item->name);
if (item->name != NULL) if (item->type != typeSubmenu && item->name != NULL)
uiprivFree(item->name); uiprivFree(item->name);
if (item->hmenus != NULL) if (item->hmenus != NULL)
uiprivFree(item->hmenus); uiprivFree(item->hmenus);