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