| |
| /* Copyright (c) Mark J. Kilgard, 1994, 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. */ |
| |
| /* The Win32 GLUT file win32_menu.c completely re-implements all |
| the menuing functionality implemented. This file is used only by |
| the X Window System version of GLUT. */ |
| |
| #ifdef __VMS |
| #include <GL/vms_x_fix.h> |
| #endif |
| |
| #include <stdlib.h> |
| #include <string.h> |
| #include <stdio.h> |
| #include <errno.h> |
| #include <assert.h> |
| |
| #include <unistd.h> |
| #include <X11/Xlib.h> |
| #include <X11/cursorfont.h> /* for XC_arrow */ |
| |
| #include "glutint.h" |
| #include "layerutil.h" |
| |
| void (CDECL *__glutMenuStatusFunc) (int, int, int); |
| GLUTmenuItem *__glutItemSelected; |
| GLUTmenu **__glutMenuList = NULL; |
| |
| static int menuListSize = 0; |
| static XFontStruct *menuFont = NULL; |
| static Cursor menuCursor; |
| static Colormap menuColormap; |
| static Visual *menuVisual; |
| static int menuDepth; |
| static int fontHeight; |
| static GC blackGC, grayGC, whiteGC; |
| static unsigned long menuBlack, menuWhite, menuGray; |
| static unsigned long useSaveUnders; |
| |
| /* A replacement for XAllocColor (originally by Brian Paul). |
| This function should never fail to allocate a color. When |
| XAllocColor fails, we return the nearest matching color. If |
| we have to allocate many colors this function isn't a great |
| solution; the XQueryColors() could be done just once. */ |
| static void |
| noFaultXAllocColor(Display * dpy, Colormap cmap, int cmapSize, |
| XColor * color) |
| { |
| XColor *ctable, subColor; |
| int i, bestmatch; |
| double mindist; /* 3*2^16^2 exceeds 32-bit long int |
| precision. */ |
| |
| for (;;) { |
| /* First try just using XAllocColor. */ |
| if (XAllocColor(dpy, cmap, color)) { |
| return; |
| } |
| |
| /* Retrieve color table entries. */ |
| /* XXX alloca canidate. */ |
| ctable = (XColor *) malloc(cmapSize * sizeof(XColor)); |
| for (i = 0; i < cmapSize; i++) |
| ctable[i].pixel = i; |
| XQueryColors(dpy, cmap, ctable, cmapSize); |
| |
| /* Find best match. */ |
| bestmatch = -1; |
| mindist = 0.0; |
| for (i = 0; i < cmapSize; i++) { |
| double dr = (double) color->red - (double) ctable[i].red; |
| double dg = (double) color->green - (double) ctable[i].green; |
| double db = (double) color->blue - (double) ctable[i].blue; |
| double dist = dr * dr + dg * dg + db * db; |
| if (bestmatch < 0 || dist < mindist) { |
| bestmatch = i; |
| mindist = dist; |
| } |
| } |
| |
| /* Return result. */ |
| subColor.red = ctable[bestmatch].red; |
| subColor.green = ctable[bestmatch].green; |
| subColor.blue = ctable[bestmatch].blue; |
| free(ctable); |
| if (XAllocColor(dpy, cmap, &subColor)) { |
| *color = subColor; |
| return; |
| } |
| /* Extremely unlikely, but possibly color was deallocated |
| and reallocated by someone else before we could |
| XAllocColor the color cell we located. If so, loop |
| again... */ |
| } |
| } |
| |
| static int |
| ifSunCreator(void) |
| { |
| char *xvendor, *glvendor, *renderer; |
| int isSunCreator = 0; /* Until proven that it is. */ |
| int savedDisplayMode = 0; |
| char *savedDisplayString = 0; |
| GLUTwindow *window; |
| |
| #define VENDOR_SUN "Sun Microsystems" |
| #define RENDERER_CREATOR "Creator" |
| |
| /* Check the X vendor string first. It is easier to check |
| than the OpenGL vendor and renderer strings since it |
| doesn't require a valid OpenGL rendering context. Bail |
| early if not connected to a Sun. */ |
| xvendor = ServerVendor(__glutDisplay); |
| if (!strncmp(xvendor, VENDOR_SUN, sizeof(VENDOR_SUN) - 1)) { |
| |
| /* We need a valid current OpenGL rendering context to be |
| able to call glGetString successfully. If there is not |
| a current window, set up a temporary one just to call |
| glGetString with (gag, expensive). */ |
| if (__glutCurrentWindow) { |
| window = NULL; |
| } else { |
| savedDisplayMode = __glutDisplayMode; |
| savedDisplayString = __glutDisplayString; |
| __glutDisplayMode = GLUT_RGB | GLUT_SINGLE; |
| __glutDisplayString = NULL; |
| window = __glutCreateWindow(NULL, 0, 0, 1, 1, 0); |
| } |
| |
| glvendor = (char *) glGetString(GL_VENDOR); |
| if (!strncmp(glvendor, VENDOR_SUN, sizeof(VENDOR_SUN) - 1)) { |
| renderer = (char *) glGetString(GL_RENDERER); |
| if (!strncmp(renderer, RENDERER_CREATOR, sizeof(RENDERER_CREATOR) - 1)) { |
| isSunCreator = 1; |
| } |
| } |
| /* Destroy the temporary window for glGetString if one |
| needed to be created. */ |
| if (window) { |
| __glutDestroyWindow(window, window); |
| __glutDisplayMode = savedDisplayMode; |
| __glutDisplayString = savedDisplayString; |
| } |
| } |
| return isSunCreator; |
| } |
| |
| static void |
| menuVisualSetup(void) |
| { |
| XLayerVisualInfo template, *visual, *overlayVisuals; |
| XColor color; |
| Status status; |
| Bool presumablyMesa; |
| int layer, nVisuals, i, dummy; |
| unsigned long *placeHolders = NULL; |
| int numPlaceHolders = 0; |
| Bool allocateHigh; |
| |
| allocateHigh = ifSunCreator(); |
| |
| /* Start with the highest overlay layer and work down. I |
| don't think any hardware has more than 3 overlay layers. */ |
| for (layer = 3; layer > 0; layer--) { |
| template.layer = layer; |
| template.vinfo.screen = __glutScreen; |
| overlayVisuals = __glutXGetLayerVisualInfo(__glutDisplay, |
| VisualScreenMask | VisualLayerMask, &template, &nVisuals); |
| if (overlayVisuals) { |
| /* First, check if the default visual is in this layer. |
| If the default visual is in this layer, we try to use |
| it since it has pre-defined black and white pixels and |
| |
| using the default visual will probably minimize |
| colormap flashing problems. Suggested by Thomas Roell |
| (thomas@xig.com). */ |
| for (i = 0; i < nVisuals; i++) { |
| visual = &overlayVisuals[i]; |
| if (visual->vinfo.colormap_size >= 3) { |
| /* Compare visual IDs just to be safe. */ |
| if (visual->vinfo.visual->visualid == DefaultVisual(__glutDisplay, __glutScreen)->visualid) { |
| /* Settle for default visual. */ |
| menuVisual = DefaultVisual(__glutDisplay, __glutScreen); |
| menuDepth = DefaultDepth(__glutDisplay, __glutScreen); |
| menuColormap = DefaultColormap(__glutDisplay, __glutScreen); |
| menuBlack = BlackPixel(__glutDisplay, __glutScreen); |
| menuWhite = WhitePixel(__glutDisplay, __glutScreen); |
| color.red = color.green = color.blue = 0xaa00; |
| noFaultXAllocColor(__glutDisplay, menuColormap, |
| menuVisual->map_entries, &color); |
| menuGray = color.pixel; |
| useSaveUnders = 0; |
| XFree(overlayVisuals); |
| return; |
| } |
| } |
| } |
| for (i = 0; i < nVisuals; i++) { |
| visual = &overlayVisuals[i]; |
| if (visual->vinfo.colormap_size >= 3) { |
| if (allocateHigh) { |
| /* For Sun's Creator graphics, try to force the |
| read-only colors to the high end of the colormap |
| by first allocating read-write place-holder cells |
| for all but the last three cells. This helps |
| avoid colormap flashing problems. */ |
| numPlaceHolders = visual->vinfo.colormap_size - 3; |
| if (numPlaceHolders > 0) { |
| placeHolders = (unsigned long *) |
| malloc(numPlaceHolders * sizeof(unsigned long)); |
| /* A malloc failure would be harmless. */ |
| } |
| } |
| menuColormap = XCreateColormap(__glutDisplay, __glutRoot, |
| visual->vinfo.visual, AllocNone); |
| if (placeHolders) { |
| /* Again for Sun's Creator graphics, do the actual |
| read-write place-holder cell allocation. */ |
| status = XAllocColorCells(__glutDisplay, menuColormap, False, 0, 0, |
| placeHolders, numPlaceHolders); |
| if (!status) { |
| XFreeColormap(__glutDisplay, menuColormap); |
| free(placeHolders); |
| continue; |
| } |
| } |
| /* Allocate overlay colormap cells in defined order: |
| gray, black, white to match the IRIS GL allocation |
| scheme. Increases likelihood of less overlay |
| colormap flashing. */ |
| /* XXX Nice if these 3 AllocColor's could be done in |
| one protocol round-trip. */ |
| color.red = color.green = color.blue = 0xaa00; |
| status = XAllocColor(__glutDisplay, |
| menuColormap, &color); |
| if (!status) { |
| XFreeColormap(__glutDisplay, menuColormap); |
| if (placeHolders) { |
| free(placeHolders); |
| } |
| continue; |
| } |
| menuGray = color.pixel; |
| color.red = color.green = color.blue = 0x0000; |
| status = XAllocColor(__glutDisplay, |
| menuColormap, &color); |
| if (!status) { |
| XFreeColormap(__glutDisplay, menuColormap); |
| if (placeHolders) { |
| free(placeHolders); |
| } |
| continue; |
| } |
| menuBlack = color.pixel; |
| color.red = color.green = color.blue = 0xffff; |
| status = XAllocColor(__glutDisplay, |
| menuColormap, &color); |
| if (!status) { |
| XFreeColormap(__glutDisplay, menuColormap); |
| if (placeHolders) { |
| free(placeHolders); |
| } |
| continue; |
| } |
| if (placeHolders) { |
| /* Now free the placeholder cells. */ |
| XFreeColors(__glutDisplay, menuColormap, |
| placeHolders, numPlaceHolders, 0); |
| free(placeHolders); |
| } |
| menuWhite = color.pixel; |
| menuVisual = visual->vinfo.visual; |
| menuDepth = visual->vinfo.depth; |
| /* If using overlays, do not request "save unders". */ |
| useSaveUnders = 0; |
| XFree(overlayVisuals); |
| return; |
| } |
| } |
| XFree(overlayVisuals); |
| } |
| } |
| /* Settle for default visual. */ |
| menuVisual = DefaultVisual(__glutDisplay, __glutScreen); |
| menuDepth = DefaultDepth(__glutDisplay, __glutScreen); |
| menuColormap = DefaultColormap(__glutDisplay, __glutScreen); |
| menuBlack = BlackPixel(__glutDisplay, __glutScreen); |
| menuWhite = WhitePixel(__glutDisplay, __glutScreen); |
| color.red = color.green = color.blue = 0xaa00; |
| noFaultXAllocColor(__glutDisplay, menuColormap, |
| menuVisual->map_entries, &color); |
| menuGray = color.pixel; |
| |
| /* When no overlays are supported, we would like to use X |
| "save unders" to avoid exposes to windows obscured by |
| pop-up menus. However, OpenGL's direct rendering support |
| means OpenGL interacts poorly with X backing store and |
| save unders. X servers do not (in implementation |
| practice) redirect OpenGL rendering destined to obscured |
| window regions into backing store. |
| |
| Implementation solutions exist for this problem, but they |
| are expensive and high-end OpenGL implementations |
| typically provide fast rendering and/or overlays to |
| obviate the problem associated of user interfaces (pop-up |
| menus) forcing redraws of complex normal plane scenes. |
| (See support for overlays pop-up menus above.) |
| |
| Mesa 3D, however, does not support direct rendering. |
| Overlays are often unavailable to Mesa, and Mesa is also |
| relatively slow. For these reasons, Mesa-rendering GLUT |
| programs can and should use X save unders. |
| |
| Look for the GLX extension. If _not_ supported, we are |
| presumably using Mesa so enable save unders. */ |
| |
| presumablyMesa = !XQueryExtension(__glutDisplay, "GLX", |
| &dummy, &dummy, &dummy); |
| |
| if (presumablyMesa) { |
| useSaveUnders = CWSaveUnder; |
| } else { |
| useSaveUnders = 0; |
| } |
| } |
| |
| static void |
| menuSetup(void) |
| { |
| if (menuFont) { |
| /* MenuFont overload to indicate menu initalization. */ |
| return; |
| } |
| menuFont = XLoadQueryFont(__glutDisplay, |
| "-*-helvetica-bold-o-normal--14-*-*-*-p-*-iso8859-1"); |
| if (!menuFont) { |
| /* Try back up font. */ |
| menuFont = XLoadQueryFont(__glutDisplay, "fixed"); |
| } |
| if (!menuFont) { |
| __glutFatalError("could not load font."); |
| } |
| menuVisualSetup(); |
| fontHeight = menuFont->ascent + menuFont->descent; |
| menuCursor = XCreateFontCursor(__glutDisplay, XC_arrow); |
| } |
| |
| static void |
| menuGraphicsContextSetup(Window win) |
| { |
| XGCValues gcvals; |
| |
| if (blackGC != None) { |
| return; |
| } |
| gcvals.font = menuFont->fid; |
| gcvals.foreground = menuBlack; |
| blackGC = XCreateGC(__glutDisplay, win, |
| GCFont | GCForeground, &gcvals); |
| gcvals.foreground = menuGray; |
| grayGC = XCreateGC(__glutDisplay, win, GCForeground, &gcvals); |
| gcvals.foreground = menuWhite; |
| whiteGC = XCreateGC(__glutDisplay, win, GCForeground, &gcvals); |
| } |
| |
| 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; |
| XUnmapWindow(__glutDisplay, menu->win); |
| } |
| |
| static void |
| finishMenu(Window win, int x, int y) |
| { |
| Window dummy; |
| int rc; |
| |
| unmapMenu(__glutMappedMenu); |
| XUngrabPointer(__glutDisplay, CurrentTime); |
| |
| /* Popping up an overlay popup menu will install its own |
| colormap. If the window associated with the menu has an |
| overlay, install that window's overlay colormap so the |
| overlay isn't left using the popup menu's colormap. */ |
| if (__glutMenuWindow->overlay) { |
| XInstallColormap(__glutDisplay, |
| __glutMenuWindow->overlay->colormap->cmap); |
| } |
| |
| /* This XFlush is needed to to make sure the pointer is |
| really ungrabbed when the application's menu callback is |
| called. Otherwise, a deadlock might happen because the |
| application may try to read from an terminal window, but |
| yet the ungrab hasn't really happened since it hasn't been |
| flushed out. */ |
| XFlush(__glutDisplay); |
| |
| if (__glutMenuStatusFunc) { |
| if (win != __glutMenuWindow->win) { |
| /* The button release may have occurred in a window other |
| than the window requesting the pop-up menu (for |
| example, one of the submenu windows). In this case, we |
| need to translate the coordinates into the coordinate |
| system of the window associated with the window. */ |
| rc = XTranslateCoordinates(__glutDisplay, win, __glutMenuWindow->win, |
| x, y, &x, &y, &dummy); |
| assert(rc != False); /* Will always be on same screen. */ |
| } |
| __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; |
| } |
| |
| #define MENU_BORDER 1 |
| #define MENU_GAP 2 |
| #define MENU_ARROW_GAP 6 |
| #define MENU_ARROW_WIDTH 8 |
| |
| static void |
| mapMenu(GLUTmenu * menu, int x, int y) |
| { |
| XWindowChanges changes; |
| unsigned int mask; |
| int subMenuExtension, num; |
| |
| /* If there are submenus, we need to provide extra space for |
| the submenu pull arrow. */ |
| if (menu->submenus > 0) { |
| subMenuExtension = MENU_ARROW_GAP + MENU_ARROW_WIDTH; |
| } else { |
| subMenuExtension = 0; |
| } |
| |
| changes.stack_mode = Above; |
| mask = CWStackMode | CWX | CWY; |
| /* If the menu isn't managed (ie, validated so all the |
| InputOnly subwindows are the right size), do so. */ |
| if (!menu->managed) { |
| GLUTmenuItem *item; |
| |
| item = menu->list; |
| num = menu->num; |
| while (item) { |
| XWindowChanges itemupdate; |
| |
| itemupdate.y = (num - 1) * fontHeight + MENU_GAP; |
| itemupdate.width = menu->pixwidth; |
| itemupdate.width += subMenuExtension; |
| XConfigureWindow(__glutDisplay, item->win, |
| CWWidth | CWY, &itemupdate); |
| item = item->next; |
| num--; |
| } |
| menu->pixheight = MENU_GAP + |
| fontHeight * menu->num + MENU_GAP; |
| changes.height = menu->pixheight; |
| changes.width = MENU_GAP + |
| menu->pixwidth + subMenuExtension + MENU_GAP; |
| mask |= CWWidth | CWHeight; |
| menu->managed = True; |
| } |
| /* Make sure menu appears fully on screen. */ |
| if (y + menu->pixheight >= __glutScreenHeight) { |
| changes.y = __glutScreenHeight - menu->pixheight; |
| } else { |
| changes.y = y; |
| } |
| if (x + menu->pixwidth + subMenuExtension >= |
| __glutScreenWidth) { |
| changes.x = __glutScreenWidth - |
| menu->pixwidth + subMenuExtension; |
| } else { |
| changes.x = x; |
| } |
| |
| /* Rember where the menu is placed so submenus can be |
| properly placed relative to it. */ |
| menu->x = changes.x; |
| menu->y = changes.y; |
| |
| XConfigureWindow(__glutDisplay, menu->win, mask, &changes); |
| XInstallColormap(__glutDisplay, menuColormap); |
| /* XXX The XRaiseWindow below should not be necessary because |
| the XConfigureWindow requests an Above stack mode (same as |
| XRaiseWindow), but some Sun users complained this was still |
| necessary. Probably some window manager or X server bug on |
| these machines?? */ |
| XRaiseWindow(__glutDisplay, menu->win); |
| XMapWindow(__glutDisplay, menu->win); |
| } |
| |
| static void |
| startMenu(GLUTmenu * menu, GLUTwindow * window, |
| int x, int y, int x_win, int y_win) |
| { |
| int grab; |
| |
| assert(__glutMappedMenu == NULL); |
| grab = XGrabPointer(__glutDisplay, __glutRoot, True, |
| ButtonPressMask | ButtonReleaseMask, |
| GrabModeAsync, GrabModeAsync, |
| __glutRoot, menuCursor, CurrentTime); |
| if (grab != GrabSuccess) { |
| /* Somebody else has pointer grabbed, ignore menu |
| activation. */ |
| return; |
| } |
| __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); |
| } |
| |
| static void |
| paintSubMenuArrow(Window win, int x, int y) |
| { |
| XPoint p[5]; |
| |
| p[0].x = p[4].x = x; |
| p[0].y = p[4].y = y - menuFont->ascent + 1; |
| p[1].x = p[0].x + MENU_ARROW_WIDTH - 1; |
| p[1].y = p[0].y + (menuFont->ascent / 2) - 1; |
| p[2].x = p[1].x; |
| p[2].y = p[1].y + 1; |
| p[3].x = p[0].x; |
| p[3].y = p[0].y + menuFont->ascent - 2; |
| XFillPolygon(__glutDisplay, win, |
| whiteGC, p, 4, Convex, CoordModeOrigin); |
| XDrawLines(__glutDisplay, win, blackGC, p, 5, CoordModeOrigin); |
| } |
| |
| static void |
| paintMenuItem(GLUTmenuItem * item, int num) |
| { |
| Window win = item->menu->win; |
| GC gc; |
| int y; |
| int subMenuExtension; |
| |
| if (item->menu->submenus > 0) { |
| subMenuExtension = MENU_ARROW_GAP + MENU_ARROW_WIDTH; |
| } else { |
| subMenuExtension = 0; |
| } |
| if (item->menu->highlighted == item) { |
| gc = whiteGC; |
| } else { |
| gc = grayGC; |
| } |
| y = MENU_GAP + fontHeight * num - menuFont->descent; |
| XFillRectangle(__glutDisplay, win, gc, |
| MENU_GAP, y - fontHeight + menuFont->descent, |
| item->menu->pixwidth + subMenuExtension, fontHeight); |
| XDrawString(__glutDisplay, win, blackGC, |
| MENU_GAP, y, item->label, item->len); |
| if (item->isTrigger) { |
| paintSubMenuArrow(win, |
| item->menu->pixwidth + MENU_ARROW_GAP + 1, y); |
| } |
| } |
| |
| static void |
| paintMenu(GLUTmenu * menu) |
| { |
| GLUTmenuItem *item; |
| int i = menu->num; |
| int y = MENU_GAP + fontHeight * i - menuFont->descent; |
| |
| item = menu->list; |
| while (item) { |
| if (item->menu->highlighted == item) { |
| paintMenuItem(item, i); |
| } else { |
| /* Quick render of the menu item; assume background |
| already cleared to gray. */ |
| XDrawString(__glutDisplay, menu->win, blackGC, |
| 2, y, item->label, item->len); |
| if (item->isTrigger) { |
| paintSubMenuArrow(menu->win, |
| menu->pixwidth + MENU_ARROW_GAP + 1, y); |
| } |
| } |
| i--; |
| y -= fontHeight; |
| item = item->next; |
| } |
| } |
| |
| static GLUTmenuItem * |
| getMenuItem(GLUTmenu * menu, Window win, int *which) |
| { |
| GLUTmenuItem *item; |
| int i; |
| |
| if (menu->searched) { |
| __glutFatalError("submenu infinite loop detected"); |
| } |
| menu->searched = True; |
| i = menu->num; |
| item = menu->list; |
| while (item) { |
| if (item->win == win) { |
| *which = i; |
| menu->searched = False; |
| return item; |
| } |
| if (item->isTrigger) { |
| GLUTmenuItem *subitem; |
| |
| subitem = __glutGetMenuItem(__glutMenuList[item->value], |
| win, which); |
| if (subitem) { |
| menu->searched = False; |
| return subitem; |
| } |
| } |
| i--; |
| item = item->next; |
| } |
| menu->searched = False; |
| return NULL; |
| } |
| |
| static int |
| getMenuItemIndex(GLUTmenuItem * item) |
| { |
| int count = 0; |
| |
| while (item) { |
| count++; |
| item = item->next; |
| } |
| return count; |
| } |
| |
| static GLUTmenu * |
| getMenu(Window win) |
| { |
| GLUTmenu *menu; |
| |
| menu = __glutMappedMenu; |
| while (menu) { |
| if (win == menu->win) { |
| return menu; |
| } |
| menu = menu->cascade; |
| } |
| return NULL; |
| } |
| |
| static GLUTmenu * |
| getMenuByNum(int menunum) |
| { |
| if (menunum < 1 || menunum > menuListSize) { |
| return NULL; |
| } |
| return __glutMenuList[menunum - 1]; |
| } |
| |
| static int |
| getUnusedMenuSlot(void) |
| { |
| int i; |
| |
| /* Look for allocated, unused slot. */ |
| for (i = 0; i < menuListSize; i++) { |
| if (!__glutMenuList[i]) { |
| return i; |
| } |
| } |
| /* Allocate a new slot. */ |
| menuListSize++; |
| if (__glutMenuList) { |
| __glutMenuList = (GLUTmenu **) |
| realloc(__glutMenuList, 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. */ |
| __glutMenuList = (GLUTmenu **) malloc(sizeof(GLUTmenu *)); |
| } |
| if (!__glutMenuList) { |
| __glutFatalError("out of memory."); |
| } |
| __glutMenuList[menuListSize - 1] = NULL; |
| return menuListSize - 1; |
| } |
| |
| void |
| __glutMenuModificationError(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."); |
| } |
| |
| |
| static void |
| menuItemEnterOrLeave(GLUTmenuItem * item, |
| int num, int type) |
| { |
| int alreadyUp = 0; |
| |
| if (type == EnterNotify) { |
| GLUTmenuItem *prevItem = item->menu->highlighted; |
| |
| if (prevItem && prevItem != item) { |
| /* If there's an already higlighted item in this menu |
| that is different from this one (we could be |
| re-entering an item with an already cascaded |
| submenu!), unhighlight the previous item. */ |
| item->menu->highlighted = NULL; |
| paintMenuItem(prevItem, getMenuItemIndex(prevItem)); |
| } |
| item->menu->highlighted = item; |
| __glutItemSelected = item; |
| if (item->menu->cascade) { |
| if (!item->isTrigger) { |
| /* Entered a menu item that is not a submenu trigger, |
| so pop down the current submenu cascade of this |
| menu. */ |
| unmapMenu(item->menu->cascade); |
| item->menu->cascade = NULL; |
| } else { |
| GLUTmenu *submenu = __glutMenuList[item->value]; |
| |
| if (submenu->anchor == item) { |
| /* We entered the submenu trigger for the submenu |
| that is already up, so don't take down the |
| submenu. */ |
| alreadyUp = 1; |
| } else { |
| /* Submenu already popped up for some other submenu |
| item of this menu; need to pop down that other |
| submenu cascade. */ |
| unmapMenu(item->menu->cascade); |
| item->menu->cascade = NULL; |
| } |
| } |
| } |
| if (!alreadyUp) { |
| /* Make sure the menu item gets painted with |
| highlighting. */ |
| paintMenuItem(item, num); |
| } else { |
| /* If already up, should already be highlighted. */ |
| } |
| } else { |
| /* LeaveNotify: Handle leaving a menu item... */ |
| if (item->menu->cascade && |
| item->menu->cascade->anchor == item) { |
| /* If there is a submenu casacaded from this item, do not |
| change the highlighting on this item upon leaving. */ |
| } else { |
| /* Unhighlight this menu item. */ |
| item->menu->highlighted = NULL; |
| paintMenuItem(item, num); |
| } |
| __glutItemSelected = NULL; |
| } |
| if (item->isTrigger) { |
| if (type == EnterNotify && !alreadyUp) { |
| GLUTmenu *submenu = __glutMenuList[item->value]; |
| |
| mapMenu(submenu, |
| item->menu->x + item->menu->pixwidth + |
| MENU_ARROW_GAP + MENU_ARROW_WIDTH + |
| MENU_GAP + MENU_BORDER, |
| item->menu->y + fontHeight * (num - 1) + MENU_GAP); |
| item->menu->cascade = submenu; |
| submenu->anchor = item; |
| } |
| } |
| } |
| |
| /* Installs callback functions for use by glut_event.c The point |
| of this is so that GLUT's menu code only gets linked into |
| GLUT binaries (assuming a static library) if the GLUT menu |
| API is used. */ |
| static void |
| installMenuCallbacks(void) |
| { |
| __glutMenuItemEnterOrLeave = menuItemEnterOrLeave; |
| __glutFinishMenu = finishMenu; |
| __glutPaintMenu = paintMenu; |
| __glutStartMenu = startMenu; |
| __glutGetMenuByNum = getMenuByNum; |
| __glutGetMenu = getMenu; |
| __glutGetMenuItem = getMenuItem; |
| } |
| |
| int GLUTAPIENTRY |
| glutCreateMenu(GLUTselectCB selectFunc) |
| { |
| XSetWindowAttributes wa; |
| GLUTmenu *menu; |
| int menuid; |
| |
| if (__glutMappedMenu) { |
| __glutMenuModificationError(); |
| } |
| if (!__glutDisplay) { |
| __glutOpenXConnection(NULL); |
| } |
| |
| installMenuCallbacks(); |
| |
| menuid = getUnusedMenuSlot(); |
| menu = (GLUTmenu *) malloc(sizeof(GLUTmenu)); |
| if (!menu) { |
| __glutFatalError("out of memory."); |
| } |
| menu->id = menuid; |
| menu->num = 0; |
| menu->submenus = 0; |
| menu->managed = False; |
| menu->searched = False; |
| menu->pixwidth = 0; |
| menu->select = selectFunc; |
| menu->list = NULL; |
| menu->cascade = NULL; |
| menu->highlighted = NULL; |
| menu->anchor = NULL; |
| menuSetup(); |
| wa.override_redirect = True; |
| wa.background_pixel = menuGray; |
| wa.border_pixel = menuBlack; |
| wa.colormap = menuColormap; |
| wa.event_mask = StructureNotifyMask | ExposureMask | |
| ButtonPressMask | ButtonReleaseMask | |
| EnterWindowMask | LeaveWindowMask; |
| /* Save unders really only enabled if useSaveUnders is set to |
| CWSaveUnder, ie. using Mesa 3D. See earlier comments. */ |
| wa.save_under = True; |
| menu->win = XCreateWindow(__glutDisplay, __glutRoot, |
| /* Real position determined when mapped. */ |
| 0, 0, |
| /* Real size will be determined when menu is manged. */ |
| 1, 1, |
| MENU_BORDER, menuDepth, InputOutput, menuVisual, |
| CWOverrideRedirect | CWBackPixel | CWBorderPixel | |
| CWEventMask | CWColormap | useSaveUnders, |
| &wa); |
| menuGraphicsContextSetup(menu->win); |
| __glutMenuList[menuid] = menu; |
| __glutSetMenu(menu); |
| return menuid + 1; |
| } |
| |
| /* CENTRY */ |
| 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 = __glutMenuList[menuid - 1]; |
| if (!menu) { |
| __glutWarning("glutSetMenu attempted on bogus menu."); |
| return; |
| } |
| __glutSetMenu(menu); |
| } |
| /* ENDCENTRY */ |
| |
| void |
| __glutSetMenuItem(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->pixwidth = XTextWidth(menuFont, label, item->len) + 4; |
| if (item->pixwidth > menu->pixwidth) { |
| menu->pixwidth = item->pixwidth; |
| } |
| menu->managed = False; |
| } |
| |
| /* CENTRY */ |
| void GLUTAPIENTRY |
| glutAddMenuEntry(const char *label, int value) |
| { |
| XSetWindowAttributes wa; |
| GLUTmenuItem *entry; |
| |
| if (__glutMappedMenu) { |
| __glutMenuModificationError(); |
| } |
| entry = (GLUTmenuItem *) malloc(sizeof(GLUTmenuItem)); |
| if (!entry) { |
| __glutFatalError("out of memory."); |
| } |
| entry->menu = __glutCurrentMenu; |
| __glutSetMenuItem(entry, label, value, False); |
| wa.event_mask = EnterWindowMask | LeaveWindowMask; |
| entry->win = XCreateWindow(__glutDisplay, |
| __glutCurrentMenu->win, MENU_GAP, |
| __glutCurrentMenu->num * fontHeight + MENU_GAP, /* x & y */ |
| entry->pixwidth, fontHeight, /* width & height */ |
| 0, CopyFromParent, InputOnly, CopyFromParent, |
| CWEventMask, &wa); |
| XMapWindow(__glutDisplay, entry->win); |
| __glutCurrentMenu->num++; |
| entry->next = __glutCurrentMenu->list; |
| __glutCurrentMenu->list = entry; |
| } |
| |
| void GLUTAPIENTRY |
| glutAddSubMenu(const char *label, int menu) |
| { |
| XSetWindowAttributes wa; |
| GLUTmenuItem *submenu; |
| |
| if (__glutMappedMenu) { |
| __glutMenuModificationError(); |
| } |
| submenu = (GLUTmenuItem *) malloc(sizeof(GLUTmenuItem)); |
| if (!submenu) { |
| __glutFatalError("out of memory."); |
| } |
| __glutCurrentMenu->submenus++; |
| submenu->menu = __glutCurrentMenu; |
| __glutSetMenuItem(submenu, label, /* base 0 */ menu - 1, True); |
| wa.event_mask = EnterWindowMask | LeaveWindowMask; |
| submenu->win = XCreateWindow(__glutDisplay, |
| __glutCurrentMenu->win, MENU_GAP, |
| __glutCurrentMenu->num * fontHeight + MENU_GAP, /* x & y */ |
| submenu->pixwidth, fontHeight, /* width & height */ |
| 0, CopyFromParent, InputOnly, CopyFromParent, |
| CWEventMask, &wa); |
| XMapWindow(__glutDisplay, submenu->win); |
| __glutCurrentMenu->num++; |
| submenu->next = __glutCurrentMenu->list; |
| __glutCurrentMenu->list = submenu; |
| } |
| |
| void GLUTAPIENTRY |
| glutAttachMenu(int button) |
| { |
| /* if button >= GLUT_MAX_MENUS, we'll go out of array bounds below */ |
| if (button >= GLUT_MAX_MENUS) { |
| return; |
| } |
| if (__glutMappedMenu) { |
| __glutMenuModificationError(); |
| } |
| installMenuCallbacks(); |
| if (__glutCurrentWindow->menu[button] < 1) { |
| __glutCurrentWindow->buttonUses++; |
| } |
| __glutChangeWindowEventMask( |
| ButtonPressMask | ButtonReleaseMask, True); |
| __glutCurrentWindow->menu[button] = __glutCurrentMenu->id + 1; |
| } |
| /* ENDCENTRY */ |