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