glut_menu.c revision c041511d
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              continue;
229            }
230          }
231          /* Allocate overlay colormap cells in defined order:
232             gray, black, white to match the IRIS GL allocation
233             scheme.  Increases likelihood of less overlay
234             colormap flashing. */
235          /* XXX Nice if these 3 AllocColor's could be done in
236             one protocol round-trip. */
237          color.red = color.green = color.blue = 0xaa00;
238          status = XAllocColor(__glutDisplay,
239            menuColormap, &color);
240          if (!status) {
241            XFreeColormap(__glutDisplay, menuColormap);
242            if (placeHolders) {
243              free(placeHolders);
244            }
245            continue;
246          }
247          menuGray = color.pixel;
248          color.red = color.green = color.blue = 0x0000;
249          status = XAllocColor(__glutDisplay,
250            menuColormap, &color);
251          if (!status) {
252            XFreeColormap(__glutDisplay, menuColormap);
253            if (placeHolders) {
254              free(placeHolders);
255            }
256            continue;
257          }
258          menuBlack = color.pixel;
259          color.red = color.green = color.blue = 0xffff;
260          status = XAllocColor(__glutDisplay,
261            menuColormap, &color);
262          if (!status) {
263            XFreeColormap(__glutDisplay, menuColormap);
264            if (placeHolders) {
265              free(placeHolders);
266            }
267            continue;
268          }
269          if (placeHolders) {
270            /* Now free the placeholder cells. */
271            XFreeColors(__glutDisplay, menuColormap,
272              placeHolders, numPlaceHolders, 0);
273            free(placeHolders);
274          }
275          menuWhite = color.pixel;
276          menuVisual = visual->vinfo.visual;
277          menuDepth = visual->vinfo.depth;
278          /* If using overlays, do not request "save unders". */
279          useSaveUnders = 0;
280          XFree(overlayVisuals);
281          return;
282        }
283      }
284      XFree(overlayVisuals);
285    }
286  }
287  /* Settle for default visual. */
288  menuVisual = DefaultVisual(__glutDisplay, __glutScreen);
289  menuDepth = DefaultDepth(__glutDisplay, __glutScreen);
290  menuColormap = DefaultColormap(__glutDisplay, __glutScreen);
291  menuBlack = BlackPixel(__glutDisplay, __glutScreen);
292  menuWhite = WhitePixel(__glutDisplay, __glutScreen);
293  color.red = color.green = color.blue = 0xaa00;
294  noFaultXAllocColor(__glutDisplay, menuColormap,
295    menuVisual->map_entries, &color);
296  menuGray = color.pixel;
297
298  /* When no overlays are supported, we would like to use X
299     "save unders" to avoid exposes to windows obscured by
300     pop-up menus.  However, OpenGL's direct rendering support
301     means OpenGL interacts poorly with X backing store and
302     save unders.  X servers do not (in implementation
303     practice) redirect OpenGL rendering destined to obscured
304     window regions into backing store.
305
306     Implementation solutions exist for this problem, but they
307     are expensive and high-end OpenGL implementations
308     typically provide fast rendering and/or overlays to
309     obviate the problem associated of user interfaces (pop-up
310     menus) forcing redraws of complex normal plane scenes.
311     (See support for overlays pop-up menus above.)
312
313     Mesa 3D, however, does not support direct rendering.
314     Overlays are often unavailable to Mesa, and Mesa is also
315     relatively slow.  For these reasons, Mesa-rendering GLUT
316     programs can and should use X save unders.
317
318     Look for the GLX extension.  If _not_ supported, we are
319     presumably using Mesa so enable save unders. */
320
321  presumablyMesa = !XQueryExtension(__glutDisplay, "GLX",
322    &dummy, &dummy, &dummy);
323
324  if (presumablyMesa) {
325    useSaveUnders = CWSaveUnder;
326  } else {
327    useSaveUnders = 0;
328  }
329}
330
331static void
332menuSetup(void)
333{
334  if (menuFont) {
335    /* MenuFont overload to indicate menu initalization. */
336    return;
337  }
338  menuFont = XLoadQueryFont(__glutDisplay,
339    "-*-helvetica-bold-o-normal--14-*-*-*-p-*-iso8859-1");
340  if (!menuFont) {
341    /* Try back up font. */
342    menuFont = XLoadQueryFont(__glutDisplay, "fixed");
343  }
344  if (!menuFont) {
345    __glutFatalError("could not load font.");
346  }
347  menuVisualSetup();
348  fontHeight = menuFont->ascent + menuFont->descent;
349  menuCursor = XCreateFontCursor(__glutDisplay, XC_arrow);
350}
351
352static void
353menuGraphicsContextSetup(Window win)
354{
355  XGCValues gcvals;
356
357  if (blackGC != None) {
358    return;
359  }
360  gcvals.font = menuFont->fid;
361  gcvals.foreground = menuBlack;
362  blackGC = XCreateGC(__glutDisplay, win,
363    GCFont | GCForeground, &gcvals);
364  gcvals.foreground = menuGray;
365  grayGC = XCreateGC(__glutDisplay, win, GCForeground, &gcvals);
366  gcvals.foreground = menuWhite;
367  whiteGC = XCreateGC(__glutDisplay, win, GCForeground, &gcvals);
368}
369
370void
371__glutSetMenu(GLUTmenu * menu)
372{
373  __glutCurrentMenu = menu;
374}
375
376static void
377unmapMenu(GLUTmenu * menu)
378{
379  if (menu->cascade) {
380    unmapMenu(menu->cascade);
381    menu->cascade = NULL;
382  }
383  menu->anchor = NULL;
384  menu->highlighted = NULL;
385  XUnmapWindow(__glutDisplay, menu->win);
386}
387
388static void
389finishMenu(Window win, int x, int y)
390{
391  Window dummy;
392  int rc;
393
394  unmapMenu(__glutMappedMenu);
395  XUngrabPointer(__glutDisplay, CurrentTime);
396
397  /* Popping up an overlay popup menu will install its own
398     colormap.  If the window associated with the menu has an
399     overlay, install that window's overlay colormap so the
400     overlay isn't left using the popup menu's colormap. */
401  if (__glutMenuWindow->overlay) {
402    XInstallColormap(__glutDisplay,
403      __glutMenuWindow->overlay->colormap->cmap);
404  }
405
406  /* This XFlush is needed to to make sure the pointer is
407     really ungrabbed when the application's menu callback is
408     called. Otherwise, a deadlock might happen because the
409     application may try to read from an terminal window, but
410     yet the ungrab hasn't really happened since it hasn't been
411     flushed out. */
412  XFlush(__glutDisplay);
413
414  if (__glutMenuStatusFunc) {
415    if (win != __glutMenuWindow->win) {
416      /* The button release may have occurred in a window other
417         than the window requesting the pop-up menu (for
418         example, one of the submenu windows).  In this case, we
419         need to translate the coordinates into the coordinate
420         system of the window associated with the window. */
421      rc = XTranslateCoordinates(__glutDisplay, win, __glutMenuWindow->win,
422        x, y, &x, &y, &dummy);
423      assert(rc != False);  /* Will always be on same screen. */
424    }
425    __glutSetWindow(__glutMenuWindow);
426    __glutSetMenu(__glutMappedMenu);
427
428    /* Setting __glutMappedMenu to NULL permits operations that
429       change menus or destroy the menu window again. */
430    __glutMappedMenu = NULL;
431
432    __glutMenuStatusFunc(GLUT_MENU_NOT_IN_USE, x, y);
433  }
434  /* Setting __glutMappedMenu to NULL permits operations that
435     change menus or destroy the menu window again. */
436  __glutMappedMenu = NULL;
437
438  /* If an item is selected and it is not a submenu trigger,
439     generate menu callback. */
440  if (__glutItemSelected && !__glutItemSelected->isTrigger) {
441    __glutSetWindow(__glutMenuWindow);
442    /* When menu callback is triggered, current menu should be
443       set to the callback menu. */
444    __glutSetMenu(__glutItemSelected->menu);
445    __glutItemSelected->menu->select(
446      __glutItemSelected->value);
447  }
448  __glutMenuWindow = NULL;
449}
450
451#define MENU_BORDER 1
452#define MENU_GAP 2
453#define MENU_ARROW_GAP 6
454#define MENU_ARROW_WIDTH 8
455
456static void
457mapMenu(GLUTmenu * menu, int x, int y)
458{
459  XWindowChanges changes;
460  unsigned int mask;
461  int subMenuExtension, num;
462
463  /* If there are submenus, we need to provide extra space for
464     the submenu pull arrow.  */
465  if (menu->submenus > 0) {
466    subMenuExtension = MENU_ARROW_GAP + MENU_ARROW_WIDTH;
467  } else {
468    subMenuExtension = 0;
469  }
470
471  changes.stack_mode = Above;
472  mask = CWStackMode | CWX | CWY;
473  /* If the menu isn't managed (ie, validated so all the
474     InputOnly subwindows are the right size), do so.  */
475  if (!menu->managed) {
476    GLUTmenuItem *item;
477
478    item = menu->list;
479    num = menu->num;
480    while (item) {
481      XWindowChanges itemupdate;
482
483      itemupdate.y = (num - 1) * fontHeight + MENU_GAP;
484      itemupdate.width = menu->pixwidth;
485      itemupdate.width += subMenuExtension;
486      XConfigureWindow(__glutDisplay, item->win,
487        CWWidth | CWY, &itemupdate);
488      item = item->next;
489      num--;
490    }
491    menu->pixheight = MENU_GAP +
492      fontHeight * menu->num + MENU_GAP;
493    changes.height = menu->pixheight;
494    changes.width = MENU_GAP +
495      menu->pixwidth + subMenuExtension + MENU_GAP;
496    mask |= CWWidth | CWHeight;
497    menu->managed = True;
498  }
499  /* Make sure menu appears fully on screen. */
500  if (y + menu->pixheight >= __glutScreenHeight) {
501    changes.y = __glutScreenHeight - menu->pixheight;
502  } else {
503    changes.y = y;
504  }
505  if (x + menu->pixwidth + subMenuExtension >=
506    __glutScreenWidth) {
507    changes.x = __glutScreenWidth -
508      menu->pixwidth + subMenuExtension;
509  } else {
510    changes.x = x;
511  }
512
513  /* Rember where the menu is placed so submenus can be
514     properly placed relative to it. */
515  menu->x = changes.x;
516  menu->y = changes.y;
517
518  XConfigureWindow(__glutDisplay, menu->win, mask, &changes);
519  XInstallColormap(__glutDisplay, menuColormap);
520  /* XXX The XRaiseWindow below should not be necessary because
521     the XConfigureWindow requests an Above stack mode (same as
522     XRaiseWindow), but some Sun users complained this was still
523     necessary.  Probably some window manager or X server bug on
524     these machines?? */
525  XRaiseWindow(__glutDisplay, menu->win);
526  XMapWindow(__glutDisplay, menu->win);
527}
528
529static void
530startMenu(GLUTmenu * menu, GLUTwindow * window,
531  int x, int y, int x_win, int y_win)
532{
533  int grab;
534
535  assert(__glutMappedMenu == NULL);
536  grab = XGrabPointer(__glutDisplay, __glutRoot, True,
537    ButtonPressMask | ButtonReleaseMask,
538    GrabModeAsync, GrabModeAsync,
539    __glutRoot, menuCursor, CurrentTime);
540  if (grab != GrabSuccess) {
541    /* Somebody else has pointer grabbed, ignore menu
542       activation. */
543    return;
544  }
545  __glutMappedMenu = menu;
546  __glutMenuWindow = window;
547  __glutItemSelected = NULL;
548  if (__glutMenuStatusFunc) {
549    __glutSetMenu(menu);
550    __glutSetWindow(window);
551    __glutMenuStatusFunc(GLUT_MENU_IN_USE, x_win, y_win);
552  }
553  mapMenu(menu, x, y);
554}
555
556static void
557paintSubMenuArrow(Window win, int x, int y)
558{
559  XPoint p[5];
560
561  p[0].x = p[4].x = x;
562  p[0].y = p[4].y = y - menuFont->ascent + 1;
563  p[1].x = p[0].x + MENU_ARROW_WIDTH - 1;
564  p[1].y = p[0].y + (menuFont->ascent / 2) - 1;
565  p[2].x = p[1].x;
566  p[2].y = p[1].y + 1;
567  p[3].x = p[0].x;
568  p[3].y = p[0].y + menuFont->ascent - 2;
569  XFillPolygon(__glutDisplay, win,
570    whiteGC, p, 4, Convex, CoordModeOrigin);
571  XDrawLines(__glutDisplay, win, blackGC, p, 5, CoordModeOrigin);
572}
573
574static void
575paintMenuItem(GLUTmenuItem * item, int num)
576{
577  Window win = item->menu->win;
578  GC gc;
579  int y;
580  int subMenuExtension;
581
582  if (item->menu->submenus > 0) {
583    subMenuExtension = MENU_ARROW_GAP + MENU_ARROW_WIDTH;
584  } else {
585    subMenuExtension = 0;
586  }
587  if (item->menu->highlighted == item) {
588    gc = whiteGC;
589  } else {
590    gc = grayGC;
591  }
592  y = MENU_GAP + fontHeight * num - menuFont->descent;
593  XFillRectangle(__glutDisplay, win, gc,
594    MENU_GAP, y - fontHeight + menuFont->descent,
595    item->menu->pixwidth + subMenuExtension, fontHeight);
596  XDrawString(__glutDisplay, win, blackGC,
597    MENU_GAP, y, item->label, item->len);
598  if (item->isTrigger) {
599    paintSubMenuArrow(win,
600      item->menu->pixwidth + MENU_ARROW_GAP + 1, y);
601  }
602}
603
604static void
605paintMenu(GLUTmenu * menu)
606{
607  GLUTmenuItem *item;
608  int i = menu->num;
609  int y = MENU_GAP + fontHeight * i - menuFont->descent;
610
611  item = menu->list;
612  while (item) {
613    if (item->menu->highlighted == item) {
614      paintMenuItem(item, i);
615    } else {
616      /* Quick render of the menu item; assume background
617         already cleared to gray. */
618      XDrawString(__glutDisplay, menu->win, blackGC,
619        2, y, item->label, item->len);
620      if (item->isTrigger) {
621        paintSubMenuArrow(menu->win,
622          menu->pixwidth + MENU_ARROW_GAP + 1, y);
623      }
624    }
625    i--;
626    y -= fontHeight;
627    item = item->next;
628  }
629}
630
631static GLUTmenuItem *
632getMenuItem(GLUTmenu * menu, Window win, int *which)
633{
634  GLUTmenuItem *item;
635  int i;
636
637  if (menu->searched) {
638    __glutFatalError("submenu infinite loop detected");
639  }
640  menu->searched = True;
641  i = menu->num;
642  item = menu->list;
643  while (item) {
644    if (item->win == win) {
645      *which = i;
646      menu->searched = False;
647      return item;
648    }
649    if (item->isTrigger) {
650      GLUTmenuItem *subitem;
651
652      subitem = __glutGetMenuItem(__glutMenuList[item->value],
653        win, which);
654      if (subitem) {
655        menu->searched = False;
656        return subitem;
657      }
658    }
659    i--;
660    item = item->next;
661  }
662  menu->searched = False;
663  return NULL;
664}
665
666static int
667getMenuItemIndex(GLUTmenuItem * item)
668{
669  int count = 0;
670
671  while (item) {
672    count++;
673    item = item->next;
674  }
675  return count;
676}
677
678static GLUTmenu *
679getMenu(Window win)
680{
681  GLUTmenu *menu;
682
683  menu = __glutMappedMenu;
684  while (menu) {
685    if (win == menu->win) {
686      return menu;
687    }
688    menu = menu->cascade;
689  }
690  return NULL;
691}
692
693static GLUTmenu *
694getMenuByNum(int menunum)
695{
696  if (menunum < 1 || menunum > menuListSize) {
697    return NULL;
698  }
699  return __glutMenuList[menunum - 1];
700}
701
702static int
703getUnusedMenuSlot(void)
704{
705  int i;
706
707  /* Look for allocated, unused slot. */
708  for (i = 0; i < menuListSize; i++) {
709    if (!__glutMenuList[i]) {
710      return i;
711    }
712  }
713  /* Allocate a new slot. */
714  menuListSize++;
715  if (__glutMenuList) {
716    __glutMenuList = (GLUTmenu **)
717      realloc(__glutMenuList, menuListSize * sizeof(GLUTmenu *));
718  } else {
719    /* XXX Some realloc's do not correctly perform a malloc
720       when asked to perform a realloc on a NULL pointer,
721       though the ANSI C library spec requires this. */
722    __glutMenuList = (GLUTmenu **) malloc(sizeof(GLUTmenu *));
723  }
724  if (!__glutMenuList) {
725    __glutFatalError("out of memory.");
726  }
727  __glutMenuList[menuListSize - 1] = NULL;
728  return menuListSize - 1;
729}
730
731void
732__glutMenuModificationError(void)
733{
734  /* XXX Remove the warning after GLUT 3.0. */
735  __glutWarning("The following is a new check for GLUT 3.0; update your code.");
736  __glutFatalError("menu manipulation not allowed while menus in use.");
737}
738
739
740static void
741menuItemEnterOrLeave(GLUTmenuItem * item,
742  int num, int type)
743{
744  int alreadyUp = 0;
745
746  if (type == EnterNotify) {
747    GLUTmenuItem *prevItem = item->menu->highlighted;
748
749    if (prevItem && prevItem != item) {
750      /* If there's an already higlighted item in this menu
751         that is different from this one (we could be
752         re-entering an item with an already cascaded
753         submenu!), unhighlight the previous item. */
754      item->menu->highlighted = NULL;
755      paintMenuItem(prevItem, getMenuItemIndex(prevItem));
756    }
757    item->menu->highlighted = item;
758    __glutItemSelected = item;
759    if (item->menu->cascade) {
760      if (!item->isTrigger) {
761        /* Entered a menu item that is not a submenu trigger,
762           so pop down the current submenu cascade of this
763           menu.  */
764        unmapMenu(item->menu->cascade);
765        item->menu->cascade = NULL;
766      } else {
767        GLUTmenu *submenu = __glutMenuList[item->value];
768
769        if (submenu->anchor == item) {
770          /* We entered the submenu trigger for the submenu
771             that is already up, so don't take down the
772             submenu.  */
773          alreadyUp = 1;
774        } else {
775          /* Submenu already popped up for some other submenu
776             item of this menu; need to pop down that other
777             submenu cascade.  */
778          unmapMenu(item->menu->cascade);
779          item->menu->cascade = NULL;
780        }
781      }
782    }
783    if (!alreadyUp) {
784      /* Make sure the menu item gets painted with
785         highlighting. */
786      paintMenuItem(item, num);
787    } else {
788      /* If already up, should already be highlighted.  */
789    }
790  } else {
791    /* LeaveNotify: Handle leaving a menu item...  */
792    if (item->menu->cascade &&
793      item->menu->cascade->anchor == item) {
794      /* If there is a submenu casacaded from this item, do not
795         change the highlighting on this item upon leaving. */
796    } else {
797      /* Unhighlight this menu item.  */
798      item->menu->highlighted = NULL;
799      paintMenuItem(item, num);
800    }
801    __glutItemSelected = NULL;
802  }
803  if (item->isTrigger) {
804    if (type == EnterNotify && !alreadyUp) {
805      GLUTmenu *submenu = __glutMenuList[item->value];
806
807      mapMenu(submenu,
808        item->menu->x + item->menu->pixwidth +
809        MENU_ARROW_GAP + MENU_ARROW_WIDTH +
810        MENU_GAP + MENU_BORDER,
811        item->menu->y + fontHeight * (num - 1) + MENU_GAP);
812      item->menu->cascade = submenu;
813      submenu->anchor = item;
814    }
815  }
816}
817
818/* Installs callback functions for use by glut_event.c  The point
819   of this is so that GLUT's menu code only gets linked into
820   GLUT binaries (assuming a static library) if the GLUT menu
821   API is used. */
822static void
823installMenuCallbacks(void)
824{
825  __glutMenuItemEnterOrLeave = menuItemEnterOrLeave;
826  __glutFinishMenu = finishMenu;
827  __glutPaintMenu = paintMenu;
828  __glutStartMenu = startMenu;
829  __glutGetMenuByNum = getMenuByNum;
830  __glutGetMenu = getMenu;
831  __glutGetMenuItem = getMenuItem;
832}
833
834int GLUTAPIENTRY
835glutCreateMenu(GLUTselectCB selectFunc)
836{
837  XSetWindowAttributes wa;
838  GLUTmenu *menu;
839  int menuid;
840
841  if (__glutMappedMenu) {
842    __glutMenuModificationError();
843  }
844  if (!__glutDisplay) {
845    __glutOpenXConnection(NULL);
846  }
847
848  installMenuCallbacks();
849
850  menuid = getUnusedMenuSlot();
851  menu = (GLUTmenu *) malloc(sizeof(GLUTmenu));
852  if (!menu) {
853    __glutFatalError("out of memory.");
854  }
855  menu->id = menuid;
856  menu->num = 0;
857  menu->submenus = 0;
858  menu->managed = False;
859  menu->searched = False;
860  menu->pixwidth = 0;
861  menu->select = selectFunc;
862  menu->list = NULL;
863  menu->cascade = NULL;
864  menu->highlighted = NULL;
865  menu->anchor = NULL;
866  menuSetup();
867  wa.override_redirect = True;
868  wa.background_pixel = menuGray;
869  wa.border_pixel = menuBlack;
870  wa.colormap = menuColormap;
871  wa.event_mask = StructureNotifyMask | ExposureMask |
872    ButtonPressMask | ButtonReleaseMask |
873    EnterWindowMask | LeaveWindowMask;
874  /* Save unders really only enabled if useSaveUnders is set to
875     CWSaveUnder, ie. using Mesa 3D.  See earlier comments. */
876  wa.save_under = True;
877  menu->win = XCreateWindow(__glutDisplay, __glutRoot,
878  /* Real position determined when mapped. */
879    0, 0,
880  /* Real size will be determined when menu is manged. */
881    1, 1,
882    MENU_BORDER, menuDepth, InputOutput, menuVisual,
883    CWOverrideRedirect | CWBackPixel | CWBorderPixel |
884    CWEventMask | CWColormap | useSaveUnders,
885    &wa);
886  menuGraphicsContextSetup(menu->win);
887  __glutMenuList[menuid] = menu;
888  __glutSetMenu(menu);
889  return menuid + 1;
890}
891
892/* CENTRY */
893int GLUTAPIENTRY
894glutGetMenu(void)
895{
896  if (__glutCurrentMenu) {
897    return __glutCurrentMenu->id + 1;
898  } else {
899    return 0;
900  }
901}
902
903void GLUTAPIENTRY
904glutSetMenu(int menuid)
905{
906  GLUTmenu *menu;
907
908  if (menuid < 1 || menuid > menuListSize) {
909    __glutWarning("glutSetMenu attempted on bogus menu.");
910    return;
911  }
912  menu = __glutMenuList[menuid - 1];
913  if (!menu) {
914    __glutWarning("glutSetMenu attempted on bogus menu.");
915    return;
916  }
917  __glutSetMenu(menu);
918}
919/* ENDCENTRY */
920
921void
922__glutSetMenuItem(GLUTmenuItem * item, const char *label,
923  int value, Bool isTrigger)
924{
925  GLUTmenu *menu;
926
927  menu = item->menu;
928  item->label = __glutStrdup(label);
929  if (!item->label) {
930    __glutFatalError("out of memory.");
931  }
932  item->isTrigger = isTrigger;
933  item->len = (int) strlen(label);
934  item->value = value;
935  item->pixwidth = XTextWidth(menuFont, label, item->len) + 4;
936  if (item->pixwidth > menu->pixwidth) {
937    menu->pixwidth = item->pixwidth;
938  }
939  menu->managed = False;
940}
941
942/* CENTRY */
943void GLUTAPIENTRY
944glutAddMenuEntry(const char *label, int value)
945{
946  XSetWindowAttributes wa;
947  GLUTmenuItem *entry;
948
949  if (__glutMappedMenu) {
950    __glutMenuModificationError();
951  }
952  entry = (GLUTmenuItem *) malloc(sizeof(GLUTmenuItem));
953  if (!entry) {
954    __glutFatalError("out of memory.");
955  }
956  entry->menu = __glutCurrentMenu;
957  __glutSetMenuItem(entry, label, value, False);
958  wa.event_mask = EnterWindowMask | LeaveWindowMask;
959  entry->win = XCreateWindow(__glutDisplay,
960    __glutCurrentMenu->win, MENU_GAP,
961    __glutCurrentMenu->num * fontHeight + MENU_GAP,  /* x & y */
962    entry->pixwidth, fontHeight,  /* width & height */
963    0, CopyFromParent, InputOnly, CopyFromParent,
964    CWEventMask, &wa);
965  XMapWindow(__glutDisplay, entry->win);
966  __glutCurrentMenu->num++;
967  entry->next = __glutCurrentMenu->list;
968  __glutCurrentMenu->list = entry;
969}
970
971void GLUTAPIENTRY
972glutAddSubMenu(const char *label, int menu)
973{
974  XSetWindowAttributes wa;
975  GLUTmenuItem *submenu;
976
977  if (__glutMappedMenu) {
978    __glutMenuModificationError();
979  }
980  submenu = (GLUTmenuItem *) malloc(sizeof(GLUTmenuItem));
981  if (!submenu) {
982    __glutFatalError("out of memory.");
983  }
984  __glutCurrentMenu->submenus++;
985  submenu->menu = __glutCurrentMenu;
986  __glutSetMenuItem(submenu, label, /* base 0 */ menu - 1, True);
987  wa.event_mask = EnterWindowMask | LeaveWindowMask;
988  submenu->win = XCreateWindow(__glutDisplay,
989    __glutCurrentMenu->win, MENU_GAP,
990    __glutCurrentMenu->num * fontHeight + MENU_GAP,  /* x & y */
991    submenu->pixwidth, fontHeight,  /* width & height */
992    0, CopyFromParent, InputOnly, CopyFromParent,
993    CWEventMask, &wa);
994  XMapWindow(__glutDisplay, submenu->win);
995  __glutCurrentMenu->num++;
996  submenu->next = __glutCurrentMenu->list;
997  __glutCurrentMenu->list = submenu;
998}
999
1000void GLUTAPIENTRY
1001glutAttachMenu(int button)
1002{
1003  /* if button >= GLUT_MAX_MENUS, we'll go out of array bounds below */
1004  if (button >= GLUT_MAX_MENUS) {
1005    return;
1006  }
1007  if (__glutMappedMenu) {
1008    __glutMenuModificationError();
1009  }
1010  installMenuCallbacks();
1011  if (__glutCurrentWindow->menu[button] < 1) {
1012    __glutCurrentWindow->buttonUses++;
1013  }
1014  __glutChangeWindowEventMask(
1015    ButtonPressMask | ButtonReleaseMask, True);
1016  __glutCurrentWindow->menu[button] = __glutCurrentMenu->id + 1;
1017}
1018/* ENDCENTRY */
1019