| |
| /* Copyright (c) Mark J. Kilgard, 1994, 1997, 1998. */ |
| /* Copyright (c) Nate Robins, 1997. */ |
| |
| /* This program is freely distributable without licensing fees |
| and is provided without guarantee or warrantee expressed or |
| implied. This program is -not- in the public domain. */ |
| |
| /* This file completely re-implements glut_menu.c and glut_menu2.c |
| for Win32. Note that neither glut_menu.c nor glut_menu2.c are |
| compiled into Win32 GLUT. */ |
| |
| #include <stdlib.h> |
| #include <string.h> |
| #include <stdio.h> |
| #include <errno.h> |
| #include <assert.h> |
| |
| #include "glutint.h" |
| |
| void (GLUTCALLBACK *__glutMenuStatusFunc) (int, int, int); |
| GLUTmenu *__glutMappedMenu; |
| GLUTwindow *__glutMenuWindow; |
| GLUTmenuItem *__glutItemSelected; |
| unsigned __glutMenuButton; |
| |
| static GLUTmenu **menuList = NULL; |
| static int menuListSize = 0; |
| static UINT uniqueMenuHandler = 1; |
| |
| /* DEPRICATED, use glutMenuStatusFunc instead. */ |
| void GLUTAPIENTRY |
| glutMenuStateFunc(GLUTmenuStateCB menuStateFunc) |
| { |
| __glutMenuStatusFunc = (GLUTmenuStatusCB) menuStateFunc; |
| } |
| |
| void GLUTAPIENTRY |
| glutMenuStatusFunc(GLUTmenuStatusCB menuStatusFunc) |
| { |
| __glutMenuStatusFunc = menuStatusFunc; |
| } |
| |
| void |
| __glutSetMenu(GLUTmenu * menu) |
| { |
| __glutCurrentMenu = menu; |
| } |
| |
| static void |
| unmapMenu(GLUTmenu * menu) |
| { |
| if (menu->cascade) { |
| unmapMenu(menu->cascade); |
| menu->cascade = NULL; |
| } |
| menu->anchor = NULL; |
| menu->highlighted = NULL; |
| } |
| |
| void |
| __glutFinishMenu(Window win, int x, int y) |
| { |
| |
| unmapMenu(__glutMappedMenu); |
| |
| /* XXX Put in a GdiFlush just in case. Probably unnecessary. -mjk */ |
| GdiFlush(); |
| |
| if (__glutMenuStatusFunc) { |
| __glutSetWindow(__glutMenuWindow); |
| __glutSetMenu(__glutMappedMenu); |
| |
| /* Setting __glutMappedMenu to NULL permits operations that |
| change menus or destroy the menu window again. */ |
| __glutMappedMenu = NULL; |
| |
| __glutMenuStatusFunc(GLUT_MENU_NOT_IN_USE, x, y); |
| } |
| /* Setting __glutMappedMenu to NULL permits operations that |
| change menus or destroy the menu window again. */ |
| __glutMappedMenu = NULL; |
| |
| /* If an item is selected and it is not a submenu trigger, |
| generate menu callback. */ |
| if (__glutItemSelected && !__glutItemSelected->isTrigger) { |
| __glutSetWindow(__glutMenuWindow); |
| /* When menu callback is triggered, current menu should be |
| set to the callback menu. */ |
| __glutSetMenu(__glutItemSelected->menu); |
| __glutItemSelected->menu->select(__glutItemSelected->value); |
| } |
| __glutMenuWindow = NULL; |
| } |
| |
| static void |
| mapMenu(GLUTmenu * menu, int x, int y) |
| { |
| TrackPopupMenu((HMENU) menu->win, TPM_LEFTALIGN | |
| (__glutMenuButton == TPM_RIGHTBUTTON) ? TPM_RIGHTBUTTON : TPM_LEFTBUTTON, |
| x, y, 0, __glutCurrentWindow->win, NULL); |
| } |
| |
| void |
| __glutStartMenu(GLUTmenu * menu, GLUTwindow * window, |
| int x, int y, int x_win, int y_win) |
| { |
| assert(__glutMappedMenu == NULL); |
| __glutMappedMenu = menu; |
| __glutMenuWindow = window; |
| __glutItemSelected = NULL; |
| if (__glutMenuStatusFunc) { |
| __glutSetMenu(menu); |
| __glutSetWindow(window); |
| __glutMenuStatusFunc(GLUT_MENU_IN_USE, x_win, y_win); |
| } |
| mapMenu(menu, x, y); |
| } |
| |
| GLUTmenuItem * |
| __glutGetUniqueMenuItem(GLUTmenu * menu, UINT unique) |
| { |
| GLUTmenuItem *item; |
| int i; |
| |
| i = menu->num; |
| item = menu->list; |
| while (item) { |
| if (item->unique == unique) { |
| return item; |
| } |
| if (item->isTrigger) { |
| GLUTmenuItem *subitem; |
| subitem = __glutGetUniqueMenuItem(menuList[item->value], unique); |
| if (subitem) { |
| return subitem; |
| } |
| } |
| i--; |
| item = item->next; |
| } |
| return NULL; |
| } |
| |
| GLUTmenuItem * |
| __glutGetMenuItem(GLUTmenu * menu, Window win, int *which) |
| { |
| GLUTmenuItem *item; |
| int i; |
| |
| i = menu->num; |
| item = menu->list; |
| while (item) { |
| if (item->win == win) { |
| *which = i; |
| return item; |
| } |
| if (item->isTrigger) { |
| GLUTmenuItem *subitem; |
| |
| subitem = __glutGetMenuItem(menuList[item->value], |
| win, which); |
| if (subitem) { |
| return subitem; |
| } |
| } |
| i--; |
| item = item->next; |
| } |
| return NULL; |
| } |
| |
| GLUTmenu * |
| __glutGetMenu(Window win) |
| { |
| GLUTmenu *menu; |
| |
| menu = __glutMappedMenu; |
| while (menu) { |
| if (win == menu->win) { |
| return menu; |
| } |
| menu = menu->cascade; |
| } |
| return NULL; |
| } |
| |
| GLUTmenu * |
| __glutGetMenuByNum(int menunum) |
| { |
| if (menunum < 1 || menunum > menuListSize) { |
| return NULL; |
| } |
| return menuList[menunum - 1]; |
| } |
| |
| static int |
| getUnusedMenuSlot(void) |
| { |
| int i; |
| |
| /* Look for allocated, unused slot. */ |
| for (i = 0; i < menuListSize; i++) { |
| if (!menuList[i]) { |
| return i; |
| } |
| } |
| /* Allocate a new slot. */ |
| menuListSize++; |
| if (menuList) { |
| menuList = (GLUTmenu **) |
| realloc(menuList, menuListSize * sizeof(GLUTmenu *)); |
| } else { |
| /* XXX Some realloc's do not correctly perform a malloc |
| when asked to perform a realloc on a NULL pointer, |
| though the ANSI C library spec requires this. */ |
| menuList = (GLUTmenu **) malloc(sizeof(GLUTmenu *)); |
| } |
| if (!menuList) { |
| __glutFatalError("out of memory."); |
| } |
| menuList[menuListSize - 1] = NULL; |
| return menuListSize - 1; |
| } |
| |
| static void |
| menuModificationError(void) |
| { |
| /* XXX Remove the warning after GLUT 3.0. */ |
| __glutWarning("The following is a new check for GLUT 3.0; update your code."); |
| __glutFatalError("menu manipulation not allowed while menus in use."); |
| } |
| |
| int GLUTAPIENTRY |
| glutCreateMenu(GLUTselectCB selectFunc) |
| { |
| GLUTmenu *menu; |
| int menuid; |
| |
| if (__glutMappedMenu) { |
| menuModificationError(); |
| } |
| menuid = getUnusedMenuSlot(); |
| menu = (GLUTmenu *) malloc(sizeof(GLUTmenu)); |
| if (!menu) { |
| __glutFatalError("out of memory."); |
| } |
| menu->id = menuid; |
| menu->num = 0; |
| menu->submenus = 0; |
| menu->select = selectFunc; |
| menu->list = NULL; |
| menu->cascade = NULL; |
| menu->highlighted = NULL; |
| menu->anchor = NULL; |
| menu->win = (HWND) CreatePopupMenu(); |
| menuList[menuid] = menu; |
| __glutSetMenu(menu); |
| return menuid + 1; |
| } |
| |
| int GLUTAPIENTRY |
| __glutCreateMenuWithExit(GLUTselectCB selectFunc, void (__cdecl *exitfunc)(int)) |
| { |
| __glutExitFunc = exitfunc; |
| return glutCreateMenu(selectFunc); |
| } |
| |
| void GLUTAPIENTRY |
| glutDestroyMenu(int menunum) |
| { |
| GLUTmenu *menu = __glutGetMenuByNum(menunum); |
| GLUTmenuItem *item, *next; |
| |
| if (__glutMappedMenu) { |
| menuModificationError(); |
| } |
| assert(menu->id == menunum - 1); |
| DestroyMenu( (HMENU) menu->win); |
| menuList[menunum - 1] = NULL; |
| /* free all menu entries */ |
| item = menu->list; |
| while (item) { |
| assert(item->menu == menu); |
| next = item->next; |
| free(item->label); |
| free(item); |
| item = next; |
| } |
| if (__glutCurrentMenu == menu) { |
| __glutCurrentMenu = NULL; |
| } |
| free(menu); |
| } |
| |
| int GLUTAPIENTRY |
| glutGetMenu(void) |
| { |
| if (__glutCurrentMenu) { |
| return __glutCurrentMenu->id + 1; |
| } else { |
| return 0; |
| } |
| } |
| |
| void GLUTAPIENTRY |
| glutSetMenu(int menuid) |
| { |
| GLUTmenu *menu; |
| |
| if (menuid < 1 || menuid > menuListSize) { |
| __glutWarning("glutSetMenu attempted on bogus menu."); |
| return; |
| } |
| menu = menuList[menuid - 1]; |
| if (!menu) { |
| __glutWarning("glutSetMenu attempted on bogus menu."); |
| return; |
| } |
| __glutSetMenu(menu); |
| } |
| |
| static void |
| setMenuItem(GLUTmenuItem * item, const char *label, |
| int value, Bool isTrigger) |
| { |
| GLUTmenu *menu; |
| |
| menu = item->menu; |
| item->label = __glutStrdup(label); |
| if (!item->label) { |
| __glutFatalError("out of memory."); |
| } |
| item->isTrigger = isTrigger; |
| item->len = (int) strlen(label); |
| item->value = value; |
| item->unique = uniqueMenuHandler++; |
| if (isTrigger) { |
| AppendMenu((HMENU) menu->win, MF_POPUP, (UINT)item->win, label); |
| } else { |
| AppendMenu((HMENU) menu->win, MF_STRING, item->unique, label); |
| } |
| } |
| |
| void GLUTAPIENTRY |
| glutAddMenuEntry(const char *label, int value) |
| { |
| GLUTmenuItem *entry; |
| |
| if (__glutMappedMenu) { |
| menuModificationError(); |
| } |
| entry = (GLUTmenuItem *) malloc(sizeof(GLUTmenuItem)); |
| if (!entry) { |
| __glutFatalError("out of memory."); |
| } |
| entry->menu = __glutCurrentMenu; |
| setMenuItem(entry, label, value, FALSE); |
| __glutCurrentMenu->num++; |
| entry->next = __glutCurrentMenu->list; |
| __glutCurrentMenu->list = entry; |
| } |
| |
| void GLUTAPIENTRY |
| glutAddSubMenu(const char *label, int menu) |
| { |
| GLUTmenuItem *submenu; |
| GLUTmenu *popupmenu; |
| |
| if (__glutMappedMenu) { |
| menuModificationError(); |
| } |
| submenu = (GLUTmenuItem *) malloc(sizeof(GLUTmenuItem)); |
| if (!submenu) { |
| __glutFatalError("out of memory."); |
| } |
| __glutCurrentMenu->submenus++; |
| submenu->menu = __glutCurrentMenu; |
| popupmenu = __glutGetMenuByNum(menu); |
| if (popupmenu) { |
| submenu->win = popupmenu->win; |
| } |
| setMenuItem(submenu, label, /* base 0 */ menu - 1, TRUE); |
| __glutCurrentMenu->num++; |
| submenu->next = __glutCurrentMenu->list; |
| __glutCurrentMenu->list = submenu; |
| } |
| |
| void GLUTAPIENTRY |
| glutChangeToMenuEntry(int num, const char *label, int value) |
| { |
| GLUTmenuItem *item; |
| int i; |
| |
| if (__glutMappedMenu) { |
| menuModificationError(); |
| } |
| i = __glutCurrentMenu->num; |
| item = __glutCurrentMenu->list; |
| while (item) { |
| if (i == num) { |
| if (item->isTrigger) { |
| /* If changing a submenu trigger to a menu entry, we |
| need to account for submenus. */ |
| item->menu->submenus--; |
| /* Nuke the Win32 menu. */ |
| DestroyMenu((HMENU) item->win); |
| } |
| free(item->label); |
| |
| item->label = strdup(label); |
| if (!item->label) |
| __glutFatalError("out of memory"); |
| item->isTrigger = FALSE; |
| item->len = (int) strlen(label); |
| item->value = value; |
| item->unique = uniqueMenuHandler++; |
| ModifyMenu((HMENU) __glutCurrentMenu->win, (UINT) i - 1, |
| MF_BYPOSITION | MFT_STRING, item->unique, label); |
| |
| return; |
| } |
| i--; |
| item = item->next; |
| } |
| __glutWarning("Current menu has no %d item.", num); |
| } |
| |
| void GLUTAPIENTRY |
| glutChangeToSubMenu(int num, const char *label, int menu) |
| { |
| GLUTmenu *popupmenu; |
| GLUTmenuItem *item; |
| int i; |
| |
| if (__glutMappedMenu) { |
| menuModificationError(); |
| } |
| i = __glutCurrentMenu->num; |
| item = __glutCurrentMenu->list; |
| while (item) { |
| if (i == num) { |
| if (!item->isTrigger) { |
| /* If changing a menu entry to as submenu trigger, we |
| need to account for submenus. */ |
| item->menu->submenus++; |
| item->win = (HWND) CreatePopupMenu(); |
| } |
| free(item->label); |
| |
| item->label = strdup(label); |
| if (!item->label) |
| __glutFatalError("out of memory"); |
| item->isTrigger = TRUE; |
| item->len = (int) strlen(label); |
| item->value = menu - 1; |
| item->unique = uniqueMenuHandler++; |
| popupmenu = __glutGetMenuByNum(menu); |
| if (popupmenu) |
| item->win = popupmenu->win; |
| ModifyMenu((HMENU) __glutCurrentMenu->win, (UINT) i - 1, |
| MF_BYPOSITION | MF_POPUP, (UINT) item->win, label); |
| return; |
| } |
| i--; |
| item = item->next; |
| } |
| __glutWarning("Current menu has no %d item.", num); |
| } |
| |
| void GLUTAPIENTRY |
| glutRemoveMenuItem(int num) |
| { |
| GLUTmenuItem *item, **prev; |
| int i; |
| |
| if (__glutMappedMenu) { |
| menuModificationError(); |
| } |
| i = __glutCurrentMenu->num; |
| prev = &__glutCurrentMenu->list; |
| item = __glutCurrentMenu->list; |
| while (item) { |
| if (i == num) { |
| /* Found the menu item in list to remove. */ |
| __glutCurrentMenu->num--; |
| |
| /* Patch up menu's item list. */ |
| *prev = item->next; |
| |
| RemoveMenu((HMENU) __glutCurrentMenu->win, (UINT) i - 1, MF_BYPOSITION); |
| |
| free(item->label); |
| free(item); |
| return; |
| } |
| i--; |
| prev = &item->next; |
| item = item->next; |
| } |
| __glutWarning("Current menu has no %d item.", num); |
| } |
| |
| void GLUTAPIENTRY |
| glutAttachMenu(int button) |
| { |
| if (__glutCurrentWindow == __glutGameModeWindow) { |
| __glutWarning("cannot attach menus in game mode."); |
| return; |
| } |
| if (__glutMappedMenu) { |
| menuModificationError(); |
| } |
| if (__glutCurrentWindow->menu[button] < 1) { |
| __glutCurrentWindow->buttonUses++; |
| } |
| __glutCurrentWindow->menu[button] = __glutCurrentMenu->id + 1; |
| } |
| |
| void GLUTAPIENTRY |
| glutDetachMenu(int button) |
| { |
| if (__glutMappedMenu) { |
| menuModificationError(); |
| } |
| if (__glutCurrentWindow->menu[button] > 0) { |
| __glutCurrentWindow->buttonUses--; |
| __glutCurrentWindow->menu[button] = 0; |
| } |
| } |
| |