1c041511dScube 2c041511dScube/* Copyright (c) Mark J. Kilgard, 1994, 1997. */ 3c041511dScube 4c041511dScube/* This program is freely distributable without licensing fees 5c041511dScube and is provided without guarantee or warrantee expressed or 6c041511dScube implied. This program is -not- in the public domain. */ 7c041511dScube 8c041511dScube/* The Win32 GLUT file win32_menu.c completely re-implements all 9c041511dScube the menuing functionality implemented. This file is used only by 10c041511dScube the X Window System version of GLUT. */ 11c041511dScube 12c041511dScube#ifdef __VMS 13c041511dScube#include <GL/vms_x_fix.h> 14c041511dScube#endif 15c041511dScube 16c041511dScube#include <stdlib.h> 17c041511dScube#include <string.h> 18c041511dScube#include <stdio.h> 19c041511dScube#include <errno.h> 20c041511dScube#include <assert.h> 21c041511dScube 22c041511dScube#include <unistd.h> 23c041511dScube#include <X11/Xlib.h> 24c041511dScube#include <X11/cursorfont.h> /* for XC_arrow */ 25c041511dScube 26c041511dScube#include "glutint.h" 27c041511dScube#include "layerutil.h" 28c041511dScube 29c041511dScubevoid (CDECL *__glutMenuStatusFunc) (int, int, int); 30c041511dScubeGLUTmenuItem *__glutItemSelected; 31c041511dScubeGLUTmenu **__glutMenuList = NULL; 32c041511dScube 33c041511dScubestatic int menuListSize = 0; 34c041511dScubestatic XFontStruct *menuFont = NULL; 35c041511dScubestatic Cursor menuCursor; 36c041511dScubestatic Colormap menuColormap; 37c041511dScubestatic Visual *menuVisual; 38c041511dScubestatic int menuDepth; 39c041511dScubestatic int fontHeight; 40c041511dScubestatic GC blackGC, grayGC, whiteGC; 41c041511dScubestatic unsigned long menuBlack, menuWhite, menuGray; 42c041511dScubestatic unsigned long useSaveUnders; 43c041511dScube 44c041511dScube/* A replacement for XAllocColor (originally by Brian Paul). 45c041511dScube This function should never fail to allocate a color. When 46c041511dScube XAllocColor fails, we return the nearest matching color. If 47c041511dScube we have to allocate many colors this function isn't a great 48c041511dScube solution; the XQueryColors() could be done just once. */ 49c041511dScubestatic void 50c041511dScubenoFaultXAllocColor(Display * dpy, Colormap cmap, int cmapSize, 51c041511dScube XColor * color) 52c041511dScube{ 53c041511dScube XColor *ctable, subColor; 54c041511dScube int i, bestmatch; 55c041511dScube double mindist; /* 3*2^16^2 exceeds 32-bit long int 56c041511dScube precision. */ 57c041511dScube 58c041511dScube for (;;) { 59c041511dScube /* First try just using XAllocColor. */ 60c041511dScube if (XAllocColor(dpy, cmap, color)) { 61c041511dScube return; 62c041511dScube } 63c041511dScube 64c041511dScube /* Retrieve color table entries. */ 65c041511dScube /* XXX alloca canidate. */ 66c041511dScube ctable = (XColor *) malloc(cmapSize * sizeof(XColor)); 67c041511dScube for (i = 0; i < cmapSize; i++) 68c041511dScube ctable[i].pixel = i; 69c041511dScube XQueryColors(dpy, cmap, ctable, cmapSize); 70c041511dScube 71c041511dScube /* Find best match. */ 72c041511dScube bestmatch = -1; 73c041511dScube mindist = 0.0; 74c041511dScube for (i = 0; i < cmapSize; i++) { 75c041511dScube double dr = (double) color->red - (double) ctable[i].red; 76c041511dScube double dg = (double) color->green - (double) ctable[i].green; 77c041511dScube double db = (double) color->blue - (double) ctable[i].blue; 78c041511dScube double dist = dr * dr + dg * dg + db * db; 79c041511dScube if (bestmatch < 0 || dist < mindist) { 80c041511dScube bestmatch = i; 81c041511dScube mindist = dist; 82c041511dScube } 83c041511dScube } 84c041511dScube 85c041511dScube /* Return result. */ 86c041511dScube subColor.red = ctable[bestmatch].red; 87c041511dScube subColor.green = ctable[bestmatch].green; 88c041511dScube subColor.blue = ctable[bestmatch].blue; 89c041511dScube free(ctable); 90c041511dScube if (XAllocColor(dpy, cmap, &subColor)) { 91c041511dScube *color = subColor; 92c041511dScube return; 93c041511dScube } 94c041511dScube /* Extremely unlikely, but possibly color was deallocated 95c041511dScube and reallocated by someone else before we could 96c041511dScube XAllocColor the color cell we located. If so, loop 97c041511dScube again... */ 98c041511dScube } 99c041511dScube} 100c041511dScube 101c041511dScubestatic int 102c041511dScubeifSunCreator(void) 103c041511dScube{ 104c041511dScube char *xvendor, *glvendor, *renderer; 105c041511dScube int isSunCreator = 0; /* Until proven that it is. */ 106c041511dScube int savedDisplayMode = 0; 107c041511dScube char *savedDisplayString = 0; 108c041511dScube GLUTwindow *window; 109c041511dScube 110c041511dScube#define VENDOR_SUN "Sun Microsystems" 111c041511dScube#define RENDERER_CREATOR "Creator" 112c041511dScube 113c041511dScube /* Check the X vendor string first. It is easier to check 114c041511dScube than the OpenGL vendor and renderer strings since it 115c041511dScube doesn't require a valid OpenGL rendering context. Bail 116c041511dScube early if not connected to a Sun. */ 117c041511dScube xvendor = ServerVendor(__glutDisplay); 118c041511dScube if (!strncmp(xvendor, VENDOR_SUN, sizeof(VENDOR_SUN) - 1)) { 119c041511dScube 120c041511dScube /* We need a valid current OpenGL rendering context to be 121c041511dScube able to call glGetString successfully. If there is not 122c041511dScube a current window, set up a temporary one just to call 123c041511dScube glGetString with (gag, expensive). */ 124c041511dScube if (__glutCurrentWindow) { 125c041511dScube window = NULL; 126c041511dScube } else { 127c041511dScube savedDisplayMode = __glutDisplayMode; 128c041511dScube savedDisplayString = __glutDisplayString; 129c041511dScube __glutDisplayMode = GLUT_RGB | GLUT_SINGLE; 130c041511dScube __glutDisplayString = NULL; 131c041511dScube window = __glutCreateWindow(NULL, 0, 0, 1, 1, 0); 132c041511dScube } 133c041511dScube 134c041511dScube glvendor = (char *) glGetString(GL_VENDOR); 135c041511dScube if (!strncmp(glvendor, VENDOR_SUN, sizeof(VENDOR_SUN) - 1)) { 136c041511dScube renderer = (char *) glGetString(GL_RENDERER); 137c041511dScube if (!strncmp(renderer, RENDERER_CREATOR, sizeof(RENDERER_CREATOR) - 1)) { 138c041511dScube isSunCreator = 1; 139c041511dScube } 140c041511dScube } 141c041511dScube /* Destroy the temporary window for glGetString if one 142c041511dScube needed to be created. */ 143c041511dScube if (window) { 144c041511dScube __glutDestroyWindow(window, window); 145c041511dScube __glutDisplayMode = savedDisplayMode; 146c041511dScube __glutDisplayString = savedDisplayString; 147c041511dScube } 148c041511dScube } 149c041511dScube return isSunCreator; 150c041511dScube} 151c041511dScube 152c041511dScubestatic void 153c041511dScubemenuVisualSetup(void) 154c041511dScube{ 155c041511dScube XLayerVisualInfo template, *visual, *overlayVisuals; 156c041511dScube XColor color; 157c041511dScube Status status; 158c041511dScube Bool presumablyMesa; 159c041511dScube int layer, nVisuals, i, dummy; 160c041511dScube unsigned long *placeHolders = NULL; 161c041511dScube int numPlaceHolders = 0; 162c041511dScube Bool allocateHigh; 163c041511dScube 164c041511dScube allocateHigh = ifSunCreator(); 165c041511dScube 166c041511dScube /* Start with the highest overlay layer and work down. I 167c041511dScube don't think any hardware has more than 3 overlay layers. */ 168c041511dScube for (layer = 3; layer > 0; layer--) { 169c041511dScube template.layer = layer; 170c041511dScube template.vinfo.screen = __glutScreen; 171c041511dScube overlayVisuals = __glutXGetLayerVisualInfo(__glutDisplay, 172c041511dScube VisualScreenMask | VisualLayerMask, &template, &nVisuals); 173c041511dScube if (overlayVisuals) { 174c041511dScube /* First, check if the default visual is in this layer. 175c041511dScube If the default visual is in this layer, we try to use 176c041511dScube it since it has pre-defined black and white pixels and 177c041511dScube 178c041511dScube using the default visual will probably minimize 179c041511dScube colormap flashing problems. Suggested by Thomas Roell 180c041511dScube (thomas@xig.com). */ 181c041511dScube for (i = 0; i < nVisuals; i++) { 182c041511dScube visual = &overlayVisuals[i]; 183c041511dScube if (visual->vinfo.colormap_size >= 3) { 184c041511dScube /* Compare visual IDs just to be safe. */ 185c041511dScube if (visual->vinfo.visual->visualid == DefaultVisual(__glutDisplay, __glutScreen)->visualid) { 186c041511dScube /* Settle for default visual. */ 187c041511dScube menuVisual = DefaultVisual(__glutDisplay, __glutScreen); 188c041511dScube menuDepth = DefaultDepth(__glutDisplay, __glutScreen); 189c041511dScube menuColormap = DefaultColormap(__glutDisplay, __glutScreen); 190c041511dScube menuBlack = BlackPixel(__glutDisplay, __glutScreen); 191c041511dScube menuWhite = WhitePixel(__glutDisplay, __glutScreen); 192c041511dScube color.red = color.green = color.blue = 0xaa00; 193c041511dScube noFaultXAllocColor(__glutDisplay, menuColormap, 194c041511dScube menuVisual->map_entries, &color); 195c041511dScube menuGray = color.pixel; 196c041511dScube useSaveUnders = 0; 197c041511dScube XFree(overlayVisuals); 198c041511dScube return; 199c041511dScube } 200c041511dScube } 201c041511dScube } 202c041511dScube for (i = 0; i < nVisuals; i++) { 203c041511dScube visual = &overlayVisuals[i]; 204c041511dScube if (visual->vinfo.colormap_size >= 3) { 205c041511dScube if (allocateHigh) { 206c041511dScube /* For Sun's Creator graphics, try to force the 207c041511dScube read-only colors to the high end of the colormap 208c041511dScube by first allocating read-write place-holder cells 209c041511dScube for all but the last three cells. This helps 210c041511dScube avoid colormap flashing problems. */ 211c041511dScube numPlaceHolders = visual->vinfo.colormap_size - 3; 212c041511dScube if (numPlaceHolders > 0) { 213c041511dScube placeHolders = (unsigned long *) 214c041511dScube malloc(numPlaceHolders * sizeof(unsigned long)); 215c041511dScube /* A malloc failure would be harmless. */ 216c041511dScube } 217c041511dScube } 218c041511dScube menuColormap = XCreateColormap(__glutDisplay, __glutRoot, 219c041511dScube visual->vinfo.visual, AllocNone); 220c041511dScube if (placeHolders) { 221c041511dScube /* Again for Sun's Creator graphics, do the actual 222c041511dScube read-write place-holder cell allocation. */ 223c041511dScube status = XAllocColorCells(__glutDisplay, menuColormap, False, 0, 0, 224c041511dScube placeHolders, numPlaceHolders); 225c041511dScube if (!status) { 226c041511dScube XFreeColormap(__glutDisplay, menuColormap); 227c041511dScube free(placeHolders); 2282590f9beSmrg placeHolders = NULL; 229c041511dScube continue; 230c041511dScube } 231c041511dScube } 232c041511dScube /* Allocate overlay colormap cells in defined order: 233c041511dScube gray, black, white to match the IRIS GL allocation 234c041511dScube scheme. Increases likelihood of less overlay 235c041511dScube colormap flashing. */ 236c041511dScube /* XXX Nice if these 3 AllocColor's could be done in 237c041511dScube one protocol round-trip. */ 238c041511dScube color.red = color.green = color.blue = 0xaa00; 239c041511dScube status = XAllocColor(__glutDisplay, 240c041511dScube menuColormap, &color); 241c041511dScube if (!status) { 242c041511dScube XFreeColormap(__glutDisplay, menuColormap); 243c041511dScube if (placeHolders) { 244c041511dScube free(placeHolders); 2452590f9beSmrg placeHolders = NULL; 246c041511dScube } 247c041511dScube continue; 248c041511dScube } 249c041511dScube menuGray = color.pixel; 250c041511dScube color.red = color.green = color.blue = 0x0000; 251c041511dScube status = XAllocColor(__glutDisplay, 252c041511dScube menuColormap, &color); 253c041511dScube if (!status) { 254c041511dScube XFreeColormap(__glutDisplay, menuColormap); 255c041511dScube if (placeHolders) { 256c041511dScube free(placeHolders); 2572590f9beSmrg placeHolders = NULL; 258c041511dScube } 259c041511dScube continue; 260c041511dScube } 261c041511dScube menuBlack = color.pixel; 262c041511dScube color.red = color.green = color.blue = 0xffff; 263c041511dScube status = XAllocColor(__glutDisplay, 264c041511dScube menuColormap, &color); 265c041511dScube if (!status) { 266c041511dScube XFreeColormap(__glutDisplay, menuColormap); 267c041511dScube if (placeHolders) { 268c041511dScube free(placeHolders); 2692590f9beSmrg placeHolders = NULL; 270c041511dScube } 271c041511dScube continue; 272c041511dScube } 273c041511dScube if (placeHolders) { 274c041511dScube /* Now free the placeholder cells. */ 275c041511dScube XFreeColors(__glutDisplay, menuColormap, 276c041511dScube placeHolders, numPlaceHolders, 0); 277c041511dScube free(placeHolders); 2782590f9beSmrg placeHolders = NULL; 279c041511dScube } 280c041511dScube menuWhite = color.pixel; 281c041511dScube menuVisual = visual->vinfo.visual; 282c041511dScube menuDepth = visual->vinfo.depth; 283c041511dScube /* If using overlays, do not request "save unders". */ 284c041511dScube useSaveUnders = 0; 285c041511dScube XFree(overlayVisuals); 286c041511dScube return; 287c041511dScube } 288c041511dScube } 289c041511dScube XFree(overlayVisuals); 290c041511dScube } 291c041511dScube } 292c041511dScube /* Settle for default visual. */ 293c041511dScube menuVisual = DefaultVisual(__glutDisplay, __glutScreen); 294c041511dScube menuDepth = DefaultDepth(__glutDisplay, __glutScreen); 295c041511dScube menuColormap = DefaultColormap(__glutDisplay, __glutScreen); 296c041511dScube menuBlack = BlackPixel(__glutDisplay, __glutScreen); 297c041511dScube menuWhite = WhitePixel(__glutDisplay, __glutScreen); 298c041511dScube color.red = color.green = color.blue = 0xaa00; 299c041511dScube noFaultXAllocColor(__glutDisplay, menuColormap, 300c041511dScube menuVisual->map_entries, &color); 301c041511dScube menuGray = color.pixel; 302c041511dScube 303c041511dScube /* When no overlays are supported, we would like to use X 304c041511dScube "save unders" to avoid exposes to windows obscured by 305c041511dScube pop-up menus. However, OpenGL's direct rendering support 306c041511dScube means OpenGL interacts poorly with X backing store and 307c041511dScube save unders. X servers do not (in implementation 308c041511dScube practice) redirect OpenGL rendering destined to obscured 309c041511dScube window regions into backing store. 310c041511dScube 311c041511dScube Implementation solutions exist for this problem, but they 312c041511dScube are expensive and high-end OpenGL implementations 313c041511dScube typically provide fast rendering and/or overlays to 314c041511dScube obviate the problem associated of user interfaces (pop-up 315c041511dScube menus) forcing redraws of complex normal plane scenes. 316c041511dScube (See support for overlays pop-up menus above.) 317c041511dScube 318c041511dScube Mesa 3D, however, does not support direct rendering. 319c041511dScube Overlays are often unavailable to Mesa, and Mesa is also 320c041511dScube relatively slow. For these reasons, Mesa-rendering GLUT 321c041511dScube programs can and should use X save unders. 322c041511dScube 323c041511dScube Look for the GLX extension. If _not_ supported, we are 324c041511dScube presumably using Mesa so enable save unders. */ 325c041511dScube 326c041511dScube presumablyMesa = !XQueryExtension(__glutDisplay, "GLX", 327c041511dScube &dummy, &dummy, &dummy); 328c041511dScube 329c041511dScube if (presumablyMesa) { 330c041511dScube useSaveUnders = CWSaveUnder; 331c041511dScube } else { 332c041511dScube useSaveUnders = 0; 333c041511dScube } 334c041511dScube} 335c041511dScube 336c041511dScubestatic void 337c041511dScubemenuSetup(void) 338c041511dScube{ 339c041511dScube if (menuFont) { 340c041511dScube /* MenuFont overload to indicate menu initalization. */ 341c041511dScube return; 342c041511dScube } 343c041511dScube menuFont = XLoadQueryFont(__glutDisplay, 344c041511dScube "-*-helvetica-bold-o-normal--14-*-*-*-p-*-iso8859-1"); 345c041511dScube if (!menuFont) { 346c041511dScube /* Try back up font. */ 347c041511dScube menuFont = XLoadQueryFont(__glutDisplay, "fixed"); 348c041511dScube } 349c041511dScube if (!menuFont) { 350c041511dScube __glutFatalError("could not load font."); 351c041511dScube } 352c041511dScube menuVisualSetup(); 353c041511dScube fontHeight = menuFont->ascent + menuFont->descent; 354c041511dScube menuCursor = XCreateFontCursor(__glutDisplay, XC_arrow); 355c041511dScube} 356c041511dScube 357c041511dScubestatic void 358c041511dScubemenuGraphicsContextSetup(Window win) 359c041511dScube{ 360c041511dScube XGCValues gcvals; 361c041511dScube 362c041511dScube if (blackGC != None) { 363c041511dScube return; 364c041511dScube } 365c041511dScube gcvals.font = menuFont->fid; 366c041511dScube gcvals.foreground = menuBlack; 367c041511dScube blackGC = XCreateGC(__glutDisplay, win, 368c041511dScube GCFont | GCForeground, &gcvals); 369c041511dScube gcvals.foreground = menuGray; 370c041511dScube grayGC = XCreateGC(__glutDisplay, win, GCForeground, &gcvals); 371c041511dScube gcvals.foreground = menuWhite; 372c041511dScube whiteGC = XCreateGC(__glutDisplay, win, GCForeground, &gcvals); 373c041511dScube} 374c041511dScube 375c041511dScubevoid 376c041511dScube__glutSetMenu(GLUTmenu * menu) 377c041511dScube{ 378c041511dScube __glutCurrentMenu = menu; 379c041511dScube} 380c041511dScube 381c041511dScubestatic void 382c041511dScubeunmapMenu(GLUTmenu * menu) 383c041511dScube{ 384c041511dScube if (menu->cascade) { 385c041511dScube unmapMenu(menu->cascade); 386c041511dScube menu->cascade = NULL; 387c041511dScube } 388c041511dScube menu->anchor = NULL; 389c041511dScube menu->highlighted = NULL; 390c041511dScube XUnmapWindow(__glutDisplay, menu->win); 391c041511dScube} 392c041511dScube 393c041511dScubestatic void 394c041511dScubefinishMenu(Window win, int x, int y) 395c041511dScube{ 396c041511dScube Window dummy; 397c041511dScube int rc; 398c041511dScube 399c041511dScube unmapMenu(__glutMappedMenu); 400c041511dScube XUngrabPointer(__glutDisplay, CurrentTime); 401c041511dScube 402c041511dScube /* Popping up an overlay popup menu will install its own 403c041511dScube colormap. If the window associated with the menu has an 404c041511dScube overlay, install that window's overlay colormap so the 405c041511dScube overlay isn't left using the popup menu's colormap. */ 406c041511dScube if (__glutMenuWindow->overlay) { 407c041511dScube XInstallColormap(__glutDisplay, 408c041511dScube __glutMenuWindow->overlay->colormap->cmap); 409c041511dScube } 410c041511dScube 411c041511dScube /* This XFlush is needed to to make sure the pointer is 412c041511dScube really ungrabbed when the application's menu callback is 413c041511dScube called. Otherwise, a deadlock might happen because the 414c041511dScube application may try to read from an terminal window, but 415c041511dScube yet the ungrab hasn't really happened since it hasn't been 416c041511dScube flushed out. */ 417c041511dScube XFlush(__glutDisplay); 418c041511dScube 419c041511dScube if (__glutMenuStatusFunc) { 420c041511dScube if (win != __glutMenuWindow->win) { 421c041511dScube /* The button release may have occurred in a window other 422c041511dScube than the window requesting the pop-up menu (for 423c041511dScube example, one of the submenu windows). In this case, we 424c041511dScube need to translate the coordinates into the coordinate 425c041511dScube system of the window associated with the window. */ 426c041511dScube rc = XTranslateCoordinates(__glutDisplay, win, __glutMenuWindow->win, 427c041511dScube x, y, &x, &y, &dummy); 428c041511dScube assert(rc != False); /* Will always be on same screen. */ 429c041511dScube } 430c041511dScube __glutSetWindow(__glutMenuWindow); 431c041511dScube __glutSetMenu(__glutMappedMenu); 432c041511dScube 433c041511dScube /* Setting __glutMappedMenu to NULL permits operations that 434c041511dScube change menus or destroy the menu window again. */ 435c041511dScube __glutMappedMenu = NULL; 436c041511dScube 437c041511dScube __glutMenuStatusFunc(GLUT_MENU_NOT_IN_USE, x, y); 438c041511dScube } 439c041511dScube /* Setting __glutMappedMenu to NULL permits operations that 440c041511dScube change menus or destroy the menu window again. */ 441c041511dScube __glutMappedMenu = NULL; 442c041511dScube 443c041511dScube /* If an item is selected and it is not a submenu trigger, 444c041511dScube generate menu callback. */ 445c041511dScube if (__glutItemSelected && !__glutItemSelected->isTrigger) { 446c041511dScube __glutSetWindow(__glutMenuWindow); 447c041511dScube /* When menu callback is triggered, current menu should be 448c041511dScube set to the callback menu. */ 449c041511dScube __glutSetMenu(__glutItemSelected->menu); 450c041511dScube __glutItemSelected->menu->select( 451c041511dScube __glutItemSelected->value); 452c041511dScube } 453c041511dScube __glutMenuWindow = NULL; 454c041511dScube} 455c041511dScube 456c041511dScube#define MENU_BORDER 1 457c041511dScube#define MENU_GAP 2 458c041511dScube#define MENU_ARROW_GAP 6 459c041511dScube#define MENU_ARROW_WIDTH 8 460c041511dScube 461c041511dScubestatic void 462c041511dScubemapMenu(GLUTmenu * menu, int x, int y) 463c041511dScube{ 464c041511dScube XWindowChanges changes; 465c041511dScube unsigned int mask; 466c041511dScube int subMenuExtension, num; 467c041511dScube 468c041511dScube /* If there are submenus, we need to provide extra space for 469c041511dScube the submenu pull arrow. */ 470c041511dScube if (menu->submenus > 0) { 471c041511dScube subMenuExtension = MENU_ARROW_GAP + MENU_ARROW_WIDTH; 472c041511dScube } else { 473c041511dScube subMenuExtension = 0; 474c041511dScube } 475c041511dScube 476c041511dScube changes.stack_mode = Above; 477c041511dScube mask = CWStackMode | CWX | CWY; 478c041511dScube /* If the menu isn't managed (ie, validated so all the 479c041511dScube InputOnly subwindows are the right size), do so. */ 480c041511dScube if (!menu->managed) { 481c041511dScube GLUTmenuItem *item; 482c041511dScube 483c041511dScube item = menu->list; 484c041511dScube num = menu->num; 485c041511dScube while (item) { 486c041511dScube XWindowChanges itemupdate; 487c041511dScube 488c041511dScube itemupdate.y = (num - 1) * fontHeight + MENU_GAP; 489c041511dScube itemupdate.width = menu->pixwidth; 490c041511dScube itemupdate.width += subMenuExtension; 491c041511dScube XConfigureWindow(__glutDisplay, item->win, 492c041511dScube CWWidth | CWY, &itemupdate); 493c041511dScube item = item->next; 494c041511dScube num--; 495c041511dScube } 496c041511dScube menu->pixheight = MENU_GAP + 497c041511dScube fontHeight * menu->num + MENU_GAP; 498c041511dScube changes.height = menu->pixheight; 499c041511dScube changes.width = MENU_GAP + 500c041511dScube menu->pixwidth + subMenuExtension + MENU_GAP; 501c041511dScube mask |= CWWidth | CWHeight; 502c041511dScube menu->managed = True; 503c041511dScube } 504c041511dScube /* Make sure menu appears fully on screen. */ 505c041511dScube if (y + menu->pixheight >= __glutScreenHeight) { 506c041511dScube changes.y = __glutScreenHeight - menu->pixheight; 507c041511dScube } else { 508c041511dScube changes.y = y; 509c041511dScube } 510c041511dScube if (x + menu->pixwidth + subMenuExtension >= 511c041511dScube __glutScreenWidth) { 512c041511dScube changes.x = __glutScreenWidth - 513c041511dScube menu->pixwidth + subMenuExtension; 514c041511dScube } else { 515c041511dScube changes.x = x; 516c041511dScube } 517c041511dScube 518c041511dScube /* Rember where the menu is placed so submenus can be 519c041511dScube properly placed relative to it. */ 520c041511dScube menu->x = changes.x; 521c041511dScube menu->y = changes.y; 522c041511dScube 523c041511dScube XConfigureWindow(__glutDisplay, menu->win, mask, &changes); 524c041511dScube XInstallColormap(__glutDisplay, menuColormap); 525c041511dScube /* XXX The XRaiseWindow below should not be necessary because 526c041511dScube the XConfigureWindow requests an Above stack mode (same as 527c041511dScube XRaiseWindow), but some Sun users complained this was still 528c041511dScube necessary. Probably some window manager or X server bug on 529c041511dScube these machines?? */ 530c041511dScube XRaiseWindow(__glutDisplay, menu->win); 531c041511dScube XMapWindow(__glutDisplay, menu->win); 532c041511dScube} 533c041511dScube 534c041511dScubestatic void 535c041511dScubestartMenu(GLUTmenu * menu, GLUTwindow * window, 536c041511dScube int x, int y, int x_win, int y_win) 537c041511dScube{ 538c041511dScube int grab; 539c041511dScube 540c041511dScube assert(__glutMappedMenu == NULL); 541c041511dScube grab = XGrabPointer(__glutDisplay, __glutRoot, True, 542c041511dScube ButtonPressMask | ButtonReleaseMask, 543c041511dScube GrabModeAsync, GrabModeAsync, 544c041511dScube __glutRoot, menuCursor, CurrentTime); 545c041511dScube if (grab != GrabSuccess) { 546c041511dScube /* Somebody else has pointer grabbed, ignore menu 547c041511dScube activation. */ 548c041511dScube return; 549c041511dScube } 550c041511dScube __glutMappedMenu = menu; 551c041511dScube __glutMenuWindow = window; 552c041511dScube __glutItemSelected = NULL; 553c041511dScube if (__glutMenuStatusFunc) { 554c041511dScube __glutSetMenu(menu); 555c041511dScube __glutSetWindow(window); 556c041511dScube __glutMenuStatusFunc(GLUT_MENU_IN_USE, x_win, y_win); 557c041511dScube } 558c041511dScube mapMenu(menu, x, y); 559c041511dScube} 560c041511dScube 561c041511dScubestatic void 562c041511dScubepaintSubMenuArrow(Window win, int x, int y) 563c041511dScube{ 564c041511dScube XPoint p[5]; 565c041511dScube 566c041511dScube p[0].x = p[4].x = x; 567c041511dScube p[0].y = p[4].y = y - menuFont->ascent + 1; 568c041511dScube p[1].x = p[0].x + MENU_ARROW_WIDTH - 1; 569c041511dScube p[1].y = p[0].y + (menuFont->ascent / 2) - 1; 570c041511dScube p[2].x = p[1].x; 571c041511dScube p[2].y = p[1].y + 1; 572c041511dScube p[3].x = p[0].x; 573c041511dScube p[3].y = p[0].y + menuFont->ascent - 2; 574c041511dScube XFillPolygon(__glutDisplay, win, 575c041511dScube whiteGC, p, 4, Convex, CoordModeOrigin); 576c041511dScube XDrawLines(__glutDisplay, win, blackGC, p, 5, CoordModeOrigin); 577c041511dScube} 578c041511dScube 579c041511dScubestatic void 580c041511dScubepaintMenuItem(GLUTmenuItem * item, int num) 581c041511dScube{ 582c041511dScube Window win = item->menu->win; 583c041511dScube GC gc; 584c041511dScube int y; 585c041511dScube int subMenuExtension; 586c041511dScube 587c041511dScube if (item->menu->submenus > 0) { 588c041511dScube subMenuExtension = MENU_ARROW_GAP + MENU_ARROW_WIDTH; 589c041511dScube } else { 590c041511dScube subMenuExtension = 0; 591c041511dScube } 592c041511dScube if (item->menu->highlighted == item) { 593c041511dScube gc = whiteGC; 594c041511dScube } else { 595c041511dScube gc = grayGC; 596c041511dScube } 597c041511dScube y = MENU_GAP + fontHeight * num - menuFont->descent; 598c041511dScube XFillRectangle(__glutDisplay, win, gc, 599c041511dScube MENU_GAP, y - fontHeight + menuFont->descent, 600c041511dScube item->menu->pixwidth + subMenuExtension, fontHeight); 601c041511dScube XDrawString(__glutDisplay, win, blackGC, 602c041511dScube MENU_GAP, y, item->label, item->len); 603c041511dScube if (item->isTrigger) { 604c041511dScube paintSubMenuArrow(win, 605c041511dScube item->menu->pixwidth + MENU_ARROW_GAP + 1, y); 606c041511dScube } 607c041511dScube} 608c041511dScube 609c041511dScubestatic void 610c041511dScubepaintMenu(GLUTmenu * menu) 611c041511dScube{ 612c041511dScube GLUTmenuItem *item; 613c041511dScube int i = menu->num; 614c041511dScube int y = MENU_GAP + fontHeight * i - menuFont->descent; 615c041511dScube 616c041511dScube item = menu->list; 617c041511dScube while (item) { 618c041511dScube if (item->menu->highlighted == item) { 619c041511dScube paintMenuItem(item, i); 620c041511dScube } else { 621c041511dScube /* Quick render of the menu item; assume background 622c041511dScube already cleared to gray. */ 623c041511dScube XDrawString(__glutDisplay, menu->win, blackGC, 624c041511dScube 2, y, item->label, item->len); 625c041511dScube if (item->isTrigger) { 626c041511dScube paintSubMenuArrow(menu->win, 627c041511dScube menu->pixwidth + MENU_ARROW_GAP + 1, y); 628c041511dScube } 629c041511dScube } 630c041511dScube i--; 631c041511dScube y -= fontHeight; 632c041511dScube item = item->next; 633c041511dScube } 634c041511dScube} 635c041511dScube 636c041511dScubestatic GLUTmenuItem * 637c041511dScubegetMenuItem(GLUTmenu * menu, Window win, int *which) 638c041511dScube{ 639c041511dScube GLUTmenuItem *item; 640c041511dScube int i; 641c041511dScube 642c041511dScube if (menu->searched) { 643c041511dScube __glutFatalError("submenu infinite loop detected"); 644c041511dScube } 645c041511dScube menu->searched = True; 646c041511dScube i = menu->num; 647c041511dScube item = menu->list; 648c041511dScube while (item) { 649c041511dScube if (item->win == win) { 650c041511dScube *which = i; 651c041511dScube menu->searched = False; 652c041511dScube return item; 653c041511dScube } 654c041511dScube if (item->isTrigger) { 655c041511dScube GLUTmenuItem *subitem; 656c041511dScube 657c041511dScube subitem = __glutGetMenuItem(__glutMenuList[item->value], 658c041511dScube win, which); 659c041511dScube if (subitem) { 660c041511dScube menu->searched = False; 661c041511dScube return subitem; 662c041511dScube } 663c041511dScube } 664c041511dScube i--; 665c041511dScube item = item->next; 666c041511dScube } 667c041511dScube menu->searched = False; 668c041511dScube return NULL; 669c041511dScube} 670c041511dScube 671c041511dScubestatic int 672c041511dScubegetMenuItemIndex(GLUTmenuItem * item) 673c041511dScube{ 674c041511dScube int count = 0; 675c041511dScube 676c041511dScube while (item) { 677c041511dScube count++; 678c041511dScube item = item->next; 679c041511dScube } 680c041511dScube return count; 681c041511dScube} 682c041511dScube 683c041511dScubestatic GLUTmenu * 684c041511dScubegetMenu(Window win) 685c041511dScube{ 686c041511dScube GLUTmenu *menu; 687c041511dScube 688c041511dScube menu = __glutMappedMenu; 689c041511dScube while (menu) { 690c041511dScube if (win == menu->win) { 691c041511dScube return menu; 692c041511dScube } 693c041511dScube menu = menu->cascade; 694c041511dScube } 695c041511dScube return NULL; 696c041511dScube} 697c041511dScube 698c041511dScubestatic GLUTmenu * 699c041511dScubegetMenuByNum(int menunum) 700c041511dScube{ 701c041511dScube if (menunum < 1 || menunum > menuListSize) { 702c041511dScube return NULL; 703c041511dScube } 704c041511dScube return __glutMenuList[menunum - 1]; 705c041511dScube} 706c041511dScube 707c041511dScubestatic int 708c041511dScubegetUnusedMenuSlot(void) 709c041511dScube{ 710c041511dScube int i; 711c041511dScube 712c041511dScube /* Look for allocated, unused slot. */ 713c041511dScube for (i = 0; i < menuListSize; i++) { 714c041511dScube if (!__glutMenuList[i]) { 715c041511dScube return i; 716c041511dScube } 717c041511dScube } 718c041511dScube /* Allocate a new slot. */ 719c041511dScube menuListSize++; 720c041511dScube if (__glutMenuList) { 721c041511dScube __glutMenuList = (GLUTmenu **) 722c041511dScube realloc(__glutMenuList, menuListSize * sizeof(GLUTmenu *)); 723c041511dScube } else { 724c041511dScube /* XXX Some realloc's do not correctly perform a malloc 725c041511dScube when asked to perform a realloc on a NULL pointer, 726c041511dScube though the ANSI C library spec requires this. */ 727c041511dScube __glutMenuList = (GLUTmenu **) malloc(sizeof(GLUTmenu *)); 728c041511dScube } 729c041511dScube if (!__glutMenuList) { 730c041511dScube __glutFatalError("out of memory."); 731c041511dScube } 732c041511dScube __glutMenuList[menuListSize - 1] = NULL; 733c041511dScube return menuListSize - 1; 734c041511dScube} 735c041511dScube 736c041511dScubevoid 737c041511dScube__glutMenuModificationError(void) 738c041511dScube{ 739c041511dScube /* XXX Remove the warning after GLUT 3.0. */ 740c041511dScube __glutWarning("The following is a new check for GLUT 3.0; update your code."); 741c041511dScube __glutFatalError("menu manipulation not allowed while menus in use."); 742c041511dScube} 743c041511dScube 744c041511dScube 745c041511dScubestatic void 746c041511dScubemenuItemEnterOrLeave(GLUTmenuItem * item, 747c041511dScube int num, int type) 748c041511dScube{ 749c041511dScube int alreadyUp = 0; 750c041511dScube 751c041511dScube if (type == EnterNotify) { 752c041511dScube GLUTmenuItem *prevItem = item->menu->highlighted; 753c041511dScube 754c041511dScube if (prevItem && prevItem != item) { 755c041511dScube /* If there's an already higlighted item in this menu 756c041511dScube that is different from this one (we could be 757c041511dScube re-entering an item with an already cascaded 758c041511dScube submenu!), unhighlight the previous item. */ 759c041511dScube item->menu->highlighted = NULL; 760c041511dScube paintMenuItem(prevItem, getMenuItemIndex(prevItem)); 761c041511dScube } 762c041511dScube item->menu->highlighted = item; 763c041511dScube __glutItemSelected = item; 764c041511dScube if (item->menu->cascade) { 765c041511dScube if (!item->isTrigger) { 766c041511dScube /* Entered a menu item that is not a submenu trigger, 767c041511dScube so pop down the current submenu cascade of this 768c041511dScube menu. */ 769c041511dScube unmapMenu(item->menu->cascade); 770c041511dScube item->menu->cascade = NULL; 771c041511dScube } else { 772c041511dScube GLUTmenu *submenu = __glutMenuList[item->value]; 773c041511dScube 774c041511dScube if (submenu->anchor == item) { 775c041511dScube /* We entered the submenu trigger for the submenu 776c041511dScube that is already up, so don't take down the 777c041511dScube submenu. */ 778c041511dScube alreadyUp = 1; 779c041511dScube } else { 780c041511dScube /* Submenu already popped up for some other submenu 781c041511dScube item of this menu; need to pop down that other 782c041511dScube submenu cascade. */ 783c041511dScube unmapMenu(item->menu->cascade); 784c041511dScube item->menu->cascade = NULL; 785c041511dScube } 786c041511dScube } 787c041511dScube } 788c041511dScube if (!alreadyUp) { 789c041511dScube /* Make sure the menu item gets painted with 790c041511dScube highlighting. */ 791c041511dScube paintMenuItem(item, num); 792c041511dScube } else { 793c041511dScube /* If already up, should already be highlighted. */ 794c041511dScube } 795c041511dScube } else { 796c041511dScube /* LeaveNotify: Handle leaving a menu item... */ 797c041511dScube if (item->menu->cascade && 798c041511dScube item->menu->cascade->anchor == item) { 799c041511dScube /* If there is a submenu casacaded from this item, do not 800c041511dScube change the highlighting on this item upon leaving. */ 801c041511dScube } else { 802c041511dScube /* Unhighlight this menu item. */ 803c041511dScube item->menu->highlighted = NULL; 804c041511dScube paintMenuItem(item, num); 805c041511dScube } 806c041511dScube __glutItemSelected = NULL; 807c041511dScube } 808c041511dScube if (item->isTrigger) { 809c041511dScube if (type == EnterNotify && !alreadyUp) { 810c041511dScube GLUTmenu *submenu = __glutMenuList[item->value]; 811c041511dScube 812c041511dScube mapMenu(submenu, 813c041511dScube item->menu->x + item->menu->pixwidth + 814c041511dScube MENU_ARROW_GAP + MENU_ARROW_WIDTH + 815c041511dScube MENU_GAP + MENU_BORDER, 816c041511dScube item->menu->y + fontHeight * (num - 1) + MENU_GAP); 817c041511dScube item->menu->cascade = submenu; 818c041511dScube submenu->anchor = item; 819c041511dScube } 820c041511dScube } 821c041511dScube} 822c041511dScube 823c041511dScube/* Installs callback functions for use by glut_event.c The point 824c041511dScube of this is so that GLUT's menu code only gets linked into 825c041511dScube GLUT binaries (assuming a static library) if the GLUT menu 826c041511dScube API is used. */ 827c041511dScubestatic void 828c041511dScubeinstallMenuCallbacks(void) 829c041511dScube{ 830c041511dScube __glutMenuItemEnterOrLeave = menuItemEnterOrLeave; 831c041511dScube __glutFinishMenu = finishMenu; 832c041511dScube __glutPaintMenu = paintMenu; 833c041511dScube __glutStartMenu = startMenu; 834c041511dScube __glutGetMenuByNum = getMenuByNum; 835c041511dScube __glutGetMenu = getMenu; 836c041511dScube __glutGetMenuItem = getMenuItem; 837c041511dScube} 838c041511dScube 839c041511dScubeint GLUTAPIENTRY 840c041511dScubeglutCreateMenu(GLUTselectCB selectFunc) 841c041511dScube{ 842c041511dScube XSetWindowAttributes wa; 843c041511dScube GLUTmenu *menu; 844c041511dScube int menuid; 845c041511dScube 846c041511dScube if (__glutMappedMenu) { 847c041511dScube __glutMenuModificationError(); 848c041511dScube } 849c041511dScube if (!__glutDisplay) { 850c041511dScube __glutOpenXConnection(NULL); 851c041511dScube } 852c041511dScube 853c041511dScube installMenuCallbacks(); 854c041511dScube 855c041511dScube menuid = getUnusedMenuSlot(); 856c041511dScube menu = (GLUTmenu *) malloc(sizeof(GLUTmenu)); 857c041511dScube if (!menu) { 858c041511dScube __glutFatalError("out of memory."); 859c041511dScube } 860c041511dScube menu->id = menuid; 861c041511dScube menu->num = 0; 862c041511dScube menu->submenus = 0; 863c041511dScube menu->managed = False; 864c041511dScube menu->searched = False; 865c041511dScube menu->pixwidth = 0; 866c041511dScube menu->select = selectFunc; 867c041511dScube menu->list = NULL; 868c041511dScube menu->cascade = NULL; 869c041511dScube menu->highlighted = NULL; 870c041511dScube menu->anchor = NULL; 871c041511dScube menuSetup(); 872c041511dScube wa.override_redirect = True; 873c041511dScube wa.background_pixel = menuGray; 874c041511dScube wa.border_pixel = menuBlack; 875c041511dScube wa.colormap = menuColormap; 876c041511dScube wa.event_mask = StructureNotifyMask | ExposureMask | 877c041511dScube ButtonPressMask | ButtonReleaseMask | 878c041511dScube EnterWindowMask | LeaveWindowMask; 879c041511dScube /* Save unders really only enabled if useSaveUnders is set to 880c041511dScube CWSaveUnder, ie. using Mesa 3D. See earlier comments. */ 881c041511dScube wa.save_under = True; 882c041511dScube menu->win = XCreateWindow(__glutDisplay, __glutRoot, 883c041511dScube /* Real position determined when mapped. */ 884c041511dScube 0, 0, 885c041511dScube /* Real size will be determined when menu is manged. */ 886c041511dScube 1, 1, 887c041511dScube MENU_BORDER, menuDepth, InputOutput, menuVisual, 888c041511dScube CWOverrideRedirect | CWBackPixel | CWBorderPixel | 889c041511dScube CWEventMask | CWColormap | useSaveUnders, 890c041511dScube &wa); 891c041511dScube menuGraphicsContextSetup(menu->win); 892c041511dScube __glutMenuList[menuid] = menu; 893c041511dScube __glutSetMenu(menu); 894c041511dScube return menuid + 1; 895c041511dScube} 896c041511dScube 897c041511dScube/* CENTRY */ 898c041511dScubeint GLUTAPIENTRY 899c041511dScubeglutGetMenu(void) 900c041511dScube{ 901c041511dScube if (__glutCurrentMenu) { 902c041511dScube return __glutCurrentMenu->id + 1; 903c041511dScube } else { 904c041511dScube return 0; 905c041511dScube } 906c041511dScube} 907c041511dScube 908c041511dScubevoid GLUTAPIENTRY 909c041511dScubeglutSetMenu(int menuid) 910c041511dScube{ 911c041511dScube GLUTmenu *menu; 912c041511dScube 913c041511dScube if (menuid < 1 || menuid > menuListSize) { 914c041511dScube __glutWarning("glutSetMenu attempted on bogus menu."); 915c041511dScube return; 916c041511dScube } 917c041511dScube menu = __glutMenuList[menuid - 1]; 918c041511dScube if (!menu) { 919c041511dScube __glutWarning("glutSetMenu attempted on bogus menu."); 920c041511dScube return; 921c041511dScube } 922c041511dScube __glutSetMenu(menu); 923c041511dScube} 924c041511dScube/* ENDCENTRY */ 925c041511dScube 926c041511dScubevoid 927c041511dScube__glutSetMenuItem(GLUTmenuItem * item, const char *label, 928c041511dScube int value, Bool isTrigger) 929c041511dScube{ 930c041511dScube GLUTmenu *menu; 931c041511dScube 932c041511dScube menu = item->menu; 933c041511dScube item->label = __glutStrdup(label); 934c041511dScube if (!item->label) { 935c041511dScube __glutFatalError("out of memory."); 936c041511dScube } 937c041511dScube item->isTrigger = isTrigger; 938c041511dScube item->len = (int) strlen(label); 939c041511dScube item->value = value; 940c041511dScube item->pixwidth = XTextWidth(menuFont, label, item->len) + 4; 941c041511dScube if (item->pixwidth > menu->pixwidth) { 942c041511dScube menu->pixwidth = item->pixwidth; 943c041511dScube } 944c041511dScube menu->managed = False; 945c041511dScube} 946c041511dScube 947c041511dScube/* CENTRY */ 948c041511dScubevoid GLUTAPIENTRY 949c041511dScubeglutAddMenuEntry(const char *label, int value) 950c041511dScube{ 951c041511dScube XSetWindowAttributes wa; 952c041511dScube GLUTmenuItem *entry; 953c041511dScube 954c041511dScube if (__glutMappedMenu) { 955c041511dScube __glutMenuModificationError(); 956c041511dScube } 957c041511dScube entry = (GLUTmenuItem *) malloc(sizeof(GLUTmenuItem)); 958c041511dScube if (!entry) { 959c041511dScube __glutFatalError("out of memory."); 960c041511dScube } 961c041511dScube entry->menu = __glutCurrentMenu; 962c041511dScube __glutSetMenuItem(entry, label, value, False); 963c041511dScube wa.event_mask = EnterWindowMask | LeaveWindowMask; 964c041511dScube entry->win = XCreateWindow(__glutDisplay, 965c041511dScube __glutCurrentMenu->win, MENU_GAP, 966c041511dScube __glutCurrentMenu->num * fontHeight + MENU_GAP, /* x & y */ 967c041511dScube entry->pixwidth, fontHeight, /* width & height */ 968c041511dScube 0, CopyFromParent, InputOnly, CopyFromParent, 969c041511dScube CWEventMask, &wa); 970c041511dScube XMapWindow(__glutDisplay, entry->win); 971c041511dScube __glutCurrentMenu->num++; 972c041511dScube entry->next = __glutCurrentMenu->list; 973c041511dScube __glutCurrentMenu->list = entry; 974c041511dScube} 975c041511dScube 976c041511dScubevoid GLUTAPIENTRY 977c041511dScubeglutAddSubMenu(const char *label, int menu) 978c041511dScube{ 979c041511dScube XSetWindowAttributes wa; 980c041511dScube GLUTmenuItem *submenu; 981c041511dScube 982c041511dScube if (__glutMappedMenu) { 983c041511dScube __glutMenuModificationError(); 984c041511dScube } 985c041511dScube submenu = (GLUTmenuItem *) malloc(sizeof(GLUTmenuItem)); 986c041511dScube if (!submenu) { 987c041511dScube __glutFatalError("out of memory."); 988c041511dScube } 989c041511dScube __glutCurrentMenu->submenus++; 990c041511dScube submenu->menu = __glutCurrentMenu; 991c041511dScube __glutSetMenuItem(submenu, label, /* base 0 */ menu - 1, True); 992c041511dScube wa.event_mask = EnterWindowMask | LeaveWindowMask; 993c041511dScube submenu->win = XCreateWindow(__glutDisplay, 994c041511dScube __glutCurrentMenu->win, MENU_GAP, 995c041511dScube __glutCurrentMenu->num * fontHeight + MENU_GAP, /* x & y */ 996c041511dScube submenu->pixwidth, fontHeight, /* width & height */ 997c041511dScube 0, CopyFromParent, InputOnly, CopyFromParent, 998c041511dScube CWEventMask, &wa); 999c041511dScube XMapWindow(__glutDisplay, submenu->win); 1000c041511dScube __glutCurrentMenu->num++; 1001c041511dScube submenu->next = __glutCurrentMenu->list; 1002c041511dScube __glutCurrentMenu->list = submenu; 1003c041511dScube} 1004c041511dScube 1005c041511dScubevoid GLUTAPIENTRY 1006c041511dScubeglutAttachMenu(int button) 1007c041511dScube{ 1008c041511dScube /* if button >= GLUT_MAX_MENUS, we'll go out of array bounds below */ 1009c041511dScube if (button >= GLUT_MAX_MENUS) { 1010c041511dScube return; 1011c041511dScube } 1012c041511dScube if (__glutMappedMenu) { 1013c041511dScube __glutMenuModificationError(); 1014c041511dScube } 1015c041511dScube installMenuCallbacks(); 1016c041511dScube if (__glutCurrentWindow->menu[button] < 1) { 1017c041511dScube __glutCurrentWindow->buttonUses++; 1018c041511dScube } 1019c041511dScube __glutChangeWindowEventMask( 1020c041511dScube ButtonPressMask | ButtonReleaseMask, True); 1021c041511dScube __glutCurrentWindow->menu[button] = __glutCurrentMenu->id + 1; 1022c041511dScube} 1023c041511dScube/* ENDCENTRY */ 1024