1 /* Copyright (c) Mark J. Kilgard, 1994, 1997. */
2 
3 /* This program is freely distributable without licensing fees
4    and is provided without guarantee or warrantee expressed or
5    implied. This program is -not- in the public domain. */
6 
7 #include <stdlib.h>
8 #include <string.h>
9 #include <stdio.h>
10 #include <errno.h>
11 #include <assert.h>
12 
13 #if !defined(WIN32)
14 #include <unistd.h>
15 #include <X11/Xlib.h>
16 #include <X11/cursorfont.h>  /* for XC_arrow */
17 #endif /* !WIN32 */
18 
19 #include <GL/glut_cgx.h>
20 #include "glutint.h"
21 #include "layerutil.h"
22 
23 GLUTmenu *__glutCurrentMenu = NULL;
24 void (*__glutMenuStatusFunc) (int, int, int);
25 GLUTmenu *__glutMappedMenu;
26 GLUTwindow *__glutMenuWindow;
27 GLUTmenuItem *__glutItemSelected;
28 
29 static GLUTmenu **menuList = NULL;
30 static int menuListSize = 0;
31 static XFontStruct *menuFont = NULL;
32 static Cursor menuCursor;
33 static Colormap menuColormap;
34 static Visual *menuVisual;
35 static int menuDepth;
36 static int fontHeight;
37 static GC blackGC, grayGC, whiteGC;
38 static unsigned long menuBlack, menuWhite, menuGray;
39 static unsigned long useSaveUnders;
40 
41 #if !defined(WIN32)
42 /* A replacement for XAllocColor (originally by Brian Paul).
43    This  function should never fail to allocate a color.  When
44    XAllocColor fails, we return the nearest matching color.  If
45    we have to allocate many colors this function isn't a great
46    solution; the XQueryColors() could be done just once.  */
47 static void
noFaultXAllocColor(Display * dpy,Colormap cmap,int cmapSize,XColor * color)48 noFaultXAllocColor(Display * dpy, Colormap cmap, int cmapSize,
49   XColor * color)
50 {
51   XColor *ctable, subColor;
52   int i, bestmatch;
53   double mindist;       /* 3*2^16^2 exceeds 32-bit long int
54                            precision. */
55 
56   for (;;) {
57     /* First try just using XAllocColor. */
58     if (XAllocColor(dpy, cmap, color))
59       return;
60 
61     /* Retrieve color table entries. */
62     /* XXX alloca canidate. */
63     ctable = (XColor *) malloc(cmapSize * sizeof(XColor));
64     for (i = 0; i < cmapSize; i++)
65       ctable[i].pixel = i;
66     XQueryColors(dpy, cmap, ctable, cmapSize);
67 
68     /* Find best match. */
69     bestmatch = -1;
70     mindist = 0.0;
71     for (i = 0; i < cmapSize; i++) {
72       double dr = (double) color->red - (double) ctable[i].red;
73       double dg = (double) color->green - (double) ctable[i].green;
74       double db = (double) color->blue - (double) ctable[i].blue;
75       double dist = dr * dr + dg * dg + db * db;
76       if (bestmatch < 0 || dist < mindist) {
77         bestmatch = i;
78         mindist = dist;
79       }
80     }
81 
82     /* Return result. */
83     subColor.red = ctable[bestmatch].red;
84     subColor.green = ctable[bestmatch].green;
85     subColor.blue = ctable[bestmatch].blue;
86     free(ctable);
87     if (XAllocColor(dpy, cmap, &subColor)) {
88       *color = subColor;
89       return;
90     }
91     /* Extremely unlikely, but possibly color was deallocated
92        and reallocated by someone else before we could
93        XAllocColor the color cell we located.  If so, loop
94        again... */
95   }
96 }
97 
98 static int
ifSunCreator(void)99 ifSunCreator(void)
100 {
101   char *xvendor, *glvendor, *renderer;
102   int isSunCreator = 0; /* Until proven that it is. */
103   int savedDisplayMode;
104   char *savedDisplayString;
105   GLUTwindow *window;
106 
107 #define VENDOR_SUN "Sun Microsystems"
108 #define RENDERER_CREATOR "Creator"
109 
110   /* Check the X vendor string first.  It is easier to check
111      than the OpenGL vendor and renderer strings since it
112      doesn't require a valid OpenGL rendering context.  Bail
113      early if not connected to a Sun. */
114   xvendor = ServerVendor(__glutDisplay);
115   if (!strncmp(xvendor, VENDOR_SUN, sizeof(VENDOR_SUN) - 1)) {
116 
117     /* We need a valid current OpenGL rendering context to be
118        able to call glGetString successfully.  If there is not
119        a current window, set up a temporary one just to call
120        glGetString with (gag, expensive). */
121     if (__glutCurrentWindow) {
122       window = NULL;
123     } else {
124       savedDisplayMode = __glutDisplayMode;
125       savedDisplayString = __glutDisplayString;
126       __glutDisplayMode = GLUT_RGB | GLUT_SINGLE;
127       __glutDisplayString = NULL;
128       window = __glutCreateWindow(NULL, 0, 0, 1, 1);
129     }
130 
131     glvendor = (char *) glGetString(GL_VENDOR);
132     if (!strncmp(glvendor, VENDOR_SUN, sizeof(VENDOR_SUN) - 1)) {
133       renderer = (char *) glGetString(GL_RENDERER);
134       if (!strncmp(renderer, RENDERER_CREATOR, sizeof(RENDERER_CREATOR) - 1)) {
135         isSunCreator = 1;
136       }
137     }
138     /* Destroy the temporary window for glGetString if one
139        needed to be created. */
140     if (window) {
141       __glutDestroyWindow(window, window);
142       __glutDisplayMode = savedDisplayMode;
143       __glutDisplayString = savedDisplayString;
144     }
145   }
146   return isSunCreator;
147 }
148 
149 static void
menuVisualSetup(void)150 menuVisualSetup(void)
151 {
152   XLayerVisualInfo template, *visual, *overlayVisuals;
153   XColor color;
154   Status status;
155   Bool presumablyMesa;
156   int layer, nVisuals, i, dummy;
157   unsigned long *placeHolders = NULL;
158   int numPlaceHolders;
159   Bool allocateHigh;
160 
161   allocateHigh = ifSunCreator();
162 
163   /* Start with the highest overlay layer and work down.  I
164      don't think any hardware has more than 3 overlay layers. */
165   for (layer = 3; layer > 0; layer--) {
166     template.layer = layer;
167     template.vinfo.screen = __glutScreen;
168     overlayVisuals = __glutXGetLayerVisualInfo(__glutDisplay,
169       VisualScreenMask | VisualLayerMask, &template, &nVisuals);
170     if (overlayVisuals) {
171       /* First, check if the default visual is in this layer.
172          If the default visual is in this layer, we try to use
173          it since it has pre-defined black and white pixels and
174 
175          using the default visual will probably minimize
176          colormap flashing problems. Suggested by Thomas Roell
177          (thomas@xig.com). */
178       for (i = 0; i < nVisuals; i++) {
179         visual = &overlayVisuals[i];
180         if (visual->vinfo.colormap_size >= 3) {
181           /* Compare visual IDs just to be safe. */
182           if (visual->vinfo.visual->visualid == DefaultVisual(__glutDisplay, __glutScreen)->visualid) {
183             /* Settle for default visual. */
184             menuVisual = DefaultVisual(__glutDisplay, __glutScreen);
185             menuDepth = DefaultDepth(__glutDisplay, __glutScreen);
186             menuColormap = DefaultColormap(__glutDisplay, __glutScreen);
187             menuBlack = BlackPixel(__glutDisplay, __glutScreen);
188             menuWhite = WhitePixel(__glutDisplay, __glutScreen);
189             color.red = color.green = color.blue = 0xaa00;
190             noFaultXAllocColor(__glutDisplay, menuColormap,
191               menuVisual->map_entries, &color);
192             menuGray = color.pixel;
193             useSaveUnders = 0;
194             XFree(overlayVisuals);
195             return;
196           }
197         }
198       }
199       for (i = 0; i < nVisuals; i++) {
200         visual = &overlayVisuals[i];
201         if (visual->vinfo.colormap_size >= 3) {
202           if (allocateHigh) {
203             /* For Sun's Creator graphics, try to force the
204                read-only colors to the high end of the colormap
205                by first allocating read-write place-holder cells
206                for all but the last three cells.  This helps
207                avoid colormap flashing problems. */
208             numPlaceHolders = visual->vinfo.colormap_size - 3;
209             if (numPlaceHolders > 0) {
210               placeHolders = (unsigned long *)
211                 malloc(numPlaceHolders * sizeof(unsigned long));
212               /* A malloc failure would be harmless. */
213             }
214           }
215           menuColormap = XCreateColormap(__glutDisplay, __glutRoot,
216             visual->vinfo.visual, AllocNone);
217           if (placeHolders) {
218             /* Again for Sun's Creator graphics, do the actual
219                read-write place-holder cell allocation. */
220             status = XAllocColorCells(__glutDisplay, menuColormap, False, 0, 0,
221               placeHolders, numPlaceHolders);
222             if (!status) {
223               XFreeColormap(__glutDisplay, menuColormap);
224               free(placeHolders);
225               continue;
226             }
227           }
228           /* Allocate overlay colormap cells in defined order:
229              gray, black, white to match the IRIS GL allocation
230              scheme.  Increases likelihood of less overlay
231              colormap flashing. */
232           /* XXX Nice if these 3 AllocColor's could be done in
233              one protocol round-trip. */
234           color.red = color.green = color.blue = 0xaa00;
235           status = XAllocColor(__glutDisplay,
236             menuColormap, &color);
237           if (!status) {
238             XFreeColormap(__glutDisplay, menuColormap);
239             if (placeHolders)
240               free(placeHolders);
241             continue;
242           }
243           menuGray = color.pixel;
244           color.red = color.green = color.blue = 0x0000;
245           status = XAllocColor(__glutDisplay,
246             menuColormap, &color);
247           if (!status) {
248             XFreeColormap(__glutDisplay, menuColormap);
249             if (placeHolders)
250               free(placeHolders);
251             continue;
252           }
253           menuBlack = color.pixel;
254           color.red = color.green = color.blue = 0xffff;
255           status = XAllocColor(__glutDisplay,
256             menuColormap, &color);
257           if (!status) {
258             XFreeColormap(__glutDisplay, menuColormap);
259             if (placeHolders)
260               free(placeHolders);
261             continue;
262           }
263           if (placeHolders) {
264             /* Now free the placeholder cells. */
265             XFreeColors(__glutDisplay, menuColormap,
266               placeHolders, numPlaceHolders, 0);
267             free(placeHolders);
268           }
269           menuWhite = color.pixel;
270           menuVisual = visual->vinfo.visual;
271           menuDepth = visual->vinfo.depth;
272           /* If using overlays, do not request "save unders". */
273           useSaveUnders = 0;
274           XFree(overlayVisuals);
275           return;
276         }
277       }
278       XFree(overlayVisuals);
279     }
280   }
281   /* Settle for default visual. */
282   menuVisual = DefaultVisual(__glutDisplay, __glutScreen);
283   menuDepth = DefaultDepth(__glutDisplay, __glutScreen);
284   menuColormap = DefaultColormap(__glutDisplay, __glutScreen);
285   menuBlack = BlackPixel(__glutDisplay, __glutScreen);
286   menuWhite = WhitePixel(__glutDisplay, __glutScreen);
287   color.red = color.green = color.blue = 0xaa00;
288   noFaultXAllocColor(__glutDisplay, menuColormap,
289     menuVisual->map_entries, &color);
290   menuGray = color.pixel;
291 
292   /* When no overlays are supported, we would like to use X
293      "save unders" to avoid exposes to windows obscured by
294      pop-up menus.  However, OpenGL's direct rendering support
295      means OpenGL interacts poorly with X backing store and
296      save unders.  X servers do not (in implementation
297      practice) redirect OpenGL rendering destined to obscured
298      window regions into backing store.
299 
300      Implementation solutions exist for this problem, but they
301      are expensive and high-end OpenGL implementations
302      typically provide fast rendering and/or overlays to
303      obviate the problem associated of user interfaces (pop-up
304      menus) forcing redraws of complex normal plane scenes.
305      (See support for overlays pop-up menus above.)
306 
307      Mesa 3D, however, does not support direct rendering.
308      Overlays are often unavailable to Mesa, and Mesa is also
309      relatively slow.  For these reasons, Mesa-rendering GLUT
310      programs can and should use X save unders.
311 
312      Look for the GLX extension.  If _not_ supported, we are
313      presumably using Mesa so enable save unders. */
314 
315   presumablyMesa = !XQueryExtension(__glutDisplay, "GLX",
316     &dummy, &dummy, &dummy);
317 
318   if (presumablyMesa)
319     useSaveUnders = CWSaveUnder;
320   else
321     useSaveUnders = 0;
322 }
323 
324 static void
menuSetup(void)325 menuSetup(void)
326 {
327   if (menuFont) {
328     /* MenuFont overload to indicate menu initalization. */
329     return;
330   }
331   menuFont = XLoadQueryFont(__glutDisplay,
332     "-*-helvetica-bold-o-normal--14-*-*-*-p-*-iso8859-1");
333   if (!menuFont) {
334     /* Try back up font. */
335     menuFont = XLoadQueryFont(__glutDisplay, "fixed");
336   }
337   if (!menuFont) {
338     __glutFatalError("could not load font.");
339   }
340   menuVisualSetup();
341   fontHeight = menuFont->ascent + menuFont->descent;
342   menuCursor = XCreateFontCursor(__glutDisplay, XC_arrow);
343 }
344 
345 static void
menuGraphicsContextSetup(Window win)346 menuGraphicsContextSetup(Window win)
347 {
348   XGCValues gcvals;
349 
350   if (blackGC != None)
351     return;
352   gcvals.font = menuFont->fid;
353   gcvals.foreground = menuBlack;
354   blackGC = XCreateGC(__glutDisplay, win,
355     GCFont | GCForeground, &gcvals);
356   gcvals.foreground = menuGray;
357   grayGC = XCreateGC(__glutDisplay, win, GCForeground, &gcvals);
358   gcvals.foreground = menuWhite;
359   whiteGC = XCreateGC(__glutDisplay, win, GCForeground, &gcvals);
360 }
361 #endif /* !WIN32 */
362 
363 /* DEPRICATED, use glutMenuStatusFunc instead. */
364 void APIENTRY
glutMenuStateFunc(GLUTmenuStateCB menuStateFunc)365 glutMenuStateFunc(GLUTmenuStateCB menuStateFunc)
366 {
367   __glutMenuStatusFunc = (GLUTmenuStatusCB) menuStateFunc;
368 }
369 
370 void APIENTRY
glutMenuStatusFunc(GLUTmenuStatusCB menuStatusFunc)371 glutMenuStatusFunc(GLUTmenuStatusCB menuStatusFunc)
372 {
373   __glutMenuStatusFunc = menuStatusFunc;
374 }
375 
376 void
__glutSetMenu(GLUTmenu * menu)377 __glutSetMenu(GLUTmenu * menu)
378 {
379   __glutCurrentMenu = menu;
380 }
381 
382 static void
unmapMenu(GLUTmenu * menu)383 unmapMenu(GLUTmenu * menu)
384 {
385   if (menu->cascade) {
386     unmapMenu(menu->cascade);
387     menu->cascade = NULL;
388   }
389   menu->anchor = NULL;
390   menu->highlighted = NULL;
391   XUnmapWindow(__glutDisplay, menu->win);
392 }
393 
394 void
__glutFinishMenu(Window win,int x,int y)395 __glutFinishMenu(Window win, int x, int y)
396 {
397   Window dummy;
398   int rc;
399 
400   unmapMenu(__glutMappedMenu);
401   XUngrabPointer(__glutDisplay, CurrentTime);
402 
403   /* Popping up an overlay popup menu will install its own
404      colormap.  If the window associated with the menu has an
405      overlay, install that window's overlay colormap so the
406      overlay isn't left using the popup menu's colormap. */
407   if (__glutMenuWindow->overlay)
408     XInstallColormap(__glutDisplay,
409       __glutMenuWindow->overlay->colormap->cmap);
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 
461 static void
mapMenu(GLUTmenu * menu,int x,int y)462 mapMenu(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 
534 void
__glutStartMenu(GLUTmenu * menu,GLUTwindow * window,int x,int y,int x_win,int y_win)535 __glutStartMenu(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 
561 static void
paintSubMenuArrow(Window win,int x,int y)562 paintSubMenuArrow(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 
579 static void
paintMenuItem(GLUTmenuItem * item,int num)580 paintMenuItem(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 
609 void
__glutPaintMenu(GLUTmenu * menu)610 __glutPaintMenu(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 
636 GLUTmenuItem *
__glutGetMenuItem(GLUTmenu * menu,Window win,int * which)637 __glutGetMenuItem(GLUTmenu * menu, Window win, int *which)
638 {
639   GLUTmenuItem *item;
640   int i;
641 
642   i = menu->num;
643   item = menu->list;
644   while (item) {
645     if (item->win == win) {
646       *which = i;
647       return item;
648     }
649     if (item->isTrigger) {
650       GLUTmenuItem *subitem;
651 
652       subitem = __glutGetMenuItem(menuList[item->value],
653         win, which);
654       if (subitem) {
655         return subitem;
656       }
657     }
658     i--;
659     item = item->next;
660   }
661   return NULL;
662 }
663 
664 static int
getMenuItemIndex(GLUTmenuItem * item)665 getMenuItemIndex(GLUTmenuItem * item)
666 {
667   int count = 0;
668 
669   while (item) {
670     count++;
671     item = item->next;
672   }
673   return count;
674 }
675 
676 GLUTmenu *
__glutGetMenu(Window win)677 __glutGetMenu(Window win)
678 {
679   GLUTmenu *menu;
680 
681   menu = __glutMappedMenu;
682   while (menu) {
683     if (win == menu->win) {
684       return menu;
685     }
686     menu = menu->cascade;
687   }
688   return NULL;
689 }
690 
691 GLUTmenu *
__glutGetMenuByNum(int menunum)692 __glutGetMenuByNum(int menunum)
693 {
694   if (menunum < 1 || menunum > menuListSize) {
695     return NULL;
696   }
697   return menuList[menunum - 1];
698 }
699 
700 static int
getUnusedMenuSlot(void)701 getUnusedMenuSlot(void)
702 {
703   int i;
704 
705   /* Look for allocated, unused slot. */
706   for (i = 0; i < menuListSize; i++) {
707     if (!menuList[i]) {
708       return i;
709     }
710   }
711   /* Allocate a new slot. */
712   menuListSize++;
713   if (menuList) {
714     menuList = (GLUTmenu **)
715       realloc(menuList, menuListSize * sizeof(GLUTmenu *));
716   } else {
717     /* XXX Some realloc's do not correctly perform a malloc
718        when asked to perform a realloc on a NULL pointer,
719        though the ANSI C library spec requires this. */
720     menuList = (GLUTmenu **) malloc(sizeof(GLUTmenu *));
721   }
722   if (!menuList)
723     __glutFatalError("out of memory.");
724   menuList[menuListSize - 1] = NULL;
725   return menuListSize - 1;
726 }
727 
728 static void
menuModificationError(void)729 menuModificationError(void)
730 {
731   /* XXX Remove the warning after GLUT 3.0. */
732   __glutWarning("The following is a new check for GLUT 3.0; update your code.");
733   __glutFatalError("menu manipulation not allowed while menus in use.");
734 }
735 
736 int APIENTRY
glutCreateMenu(GLUTselectCB selectFunc)737 glutCreateMenu(GLUTselectCB selectFunc)
738 {
739   XSetWindowAttributes wa;
740   GLUTmenu *menu;
741   int menuid;
742 
743   if (__glutMappedMenu)
744     menuModificationError();
745   if (!__glutDisplay)
746     __glutOpenXConnection(NULL);
747   menuid = getUnusedMenuSlot();
748   menu = (GLUTmenu *) malloc(sizeof(GLUTmenu));
749   if (!menu)
750     __glutFatalError("out of memory.");
751   menu->id = menuid;
752   menu->num = 0;
753   menu->submenus = 0;
754   menu->managed = False;
755   menu->pixwidth = 0;
756   menu->select = selectFunc;
757   menu->list = NULL;
758   menu->cascade = NULL;
759   menu->highlighted = NULL;
760   menu->anchor = NULL;
761   menuSetup();
762   wa.override_redirect = True;
763   wa.background_pixel = menuGray;
764   wa.border_pixel = menuBlack;
765   wa.colormap = menuColormap;
766   wa.event_mask = StructureNotifyMask | ExposureMask |
767     ButtonPressMask | ButtonReleaseMask |
768     EnterWindowMask | LeaveWindowMask;
769   /* Save unders really only enabled if useSaveUnders is set to
770      CWSaveUnder, ie. using Mesa 3D.  See earlier comments. */
771   wa.save_under = True;
772   menu->win = XCreateWindow(__glutDisplay, __glutRoot,
773   /* Real position determined when mapped. */
774     0, 0,
775   /* Real size will be determined when menu is manged. */
776     1, 1,
777     MENU_BORDER, menuDepth, InputOutput, menuVisual,
778     CWOverrideRedirect | CWBackPixel | CWBorderPixel |
779     CWEventMask | CWColormap | useSaveUnders,
780     &wa);
781   menuGraphicsContextSetup(menu->win);
782   menuList[menuid] = menu;
783   __glutSetMenu(menu);
784   return menuid + 1;
785 }
786 
787 /* CENTRY */
788 void APIENTRY
glutDestroyMenu(int menunum)789 glutDestroyMenu(int menunum)
790 {
791   GLUTmenu *menu = __glutGetMenuByNum(menunum);
792   GLUTmenuItem *item, *next;
793 
794   if (__glutMappedMenu)
795     menuModificationError();
796   assert(menu->id == menunum - 1);
797   XDestroySubwindows(__glutDisplay, menu->win);
798   XDestroyWindow(__glutDisplay, menu->win);
799   menuList[menunum - 1] = NULL;
800   /* free all menu entries */
801   item = menu->list;
802   while (item) {
803     assert(item->menu == menu);
804     next = item->next;
805     free(item->label);
806     free(item);
807     item = next;
808   }
809   if (__glutCurrentMenu == menu) {
810     __glutCurrentMenu = NULL;
811   }
812   free(menu);
813 }
814 
815 int APIENTRY
glutGetMenu(void)816 glutGetMenu(void)
817 {
818   if (__glutCurrentMenu) {
819     return __glutCurrentMenu->id + 1;
820   } else {
821     return 0;
822   }
823 }
824 
825 void APIENTRY
glutSetMenu(int menuid)826 glutSetMenu(int menuid)
827 {
828   GLUTmenu *menu;
829 
830   if (menuid < 1 || menuid > menuListSize) {
831     __glutWarning("glutSetMenu attempted on bogus menu.");
832     return;
833   }
834   menu = menuList[menuid - 1];
835   if (!menu) {
836     __glutWarning("glutSetMenu attempted on bogus menu.");
837     return;
838   }
839   __glutSetMenu(menu);
840 }
841 /* ENDCENTRY */
842 
843 static void
setMenuItem(GLUTmenuItem * item,const char * label,int value,Bool isTrigger)844 setMenuItem(GLUTmenuItem * item, const char *label,
845   int value, Bool isTrigger)
846 {
847   GLUTmenu *menu;
848 
849   menu = item->menu;
850   item->label = strdup(label);
851   if (!item->label)
852     __glutFatalError("out of memory.");
853   item->isTrigger = isTrigger;
854   item->len = (int) strlen(label);
855   item->value = value;
856   item->pixwidth = XTextWidth(menuFont, label, item->len) + 4;
857   if (item->pixwidth > menu->pixwidth) {
858     menu->pixwidth = item->pixwidth;
859   }
860   menu->managed = False;
861 }
862 
863 /* CENTRY */
864 void APIENTRY
glutAddMenuEntry(const char * label,int value)865 glutAddMenuEntry(const char *label, int value)
866 {
867   XSetWindowAttributes wa;
868   GLUTmenuItem *entry;
869 
870   if (__glutMappedMenu)
871     menuModificationError();
872   entry = (GLUTmenuItem *) malloc(sizeof(GLUTmenuItem));
873   if (!entry)
874     __glutFatalError("out of memory.");
875   entry->menu = __glutCurrentMenu;
876   setMenuItem(entry, label, value, False);
877   wa.event_mask = EnterWindowMask | LeaveWindowMask;
878   entry->win = XCreateWindow(__glutDisplay,
879     __glutCurrentMenu->win, MENU_GAP,
880     __glutCurrentMenu->num * fontHeight + MENU_GAP,  /* x & y */
881     entry->pixwidth, fontHeight,  /* width & height */
882     0, CopyFromParent, InputOnly, CopyFromParent,
883     CWEventMask, &wa);
884   XMapWindow(__glutDisplay, entry->win);
885   __glutCurrentMenu->num++;
886   entry->next = __glutCurrentMenu->list;
887   __glutCurrentMenu->list = entry;
888 }
889 
890 void APIENTRY
glutAddSubMenu(const char * label,int menu)891 glutAddSubMenu(const char *label, int menu)
892 {
893   XSetWindowAttributes wa;
894   GLUTmenuItem *submenu;
895 
896   if (__glutMappedMenu)
897     menuModificationError();
898   submenu = (GLUTmenuItem *) malloc(sizeof(GLUTmenuItem));
899   if (!submenu)
900     __glutFatalError("out of memory.");
901   __glutCurrentMenu->submenus++;
902   submenu->menu = __glutCurrentMenu;
903   setMenuItem(submenu, label, /* base 0 */ menu - 1, True);
904   wa.event_mask = EnterWindowMask | LeaveWindowMask;
905   submenu->win = XCreateWindow(__glutDisplay,
906     __glutCurrentMenu->win, MENU_GAP,
907     __glutCurrentMenu->num * fontHeight + MENU_GAP,  /* x & y */
908     submenu->pixwidth, fontHeight,  /* width & height */
909     0, CopyFromParent, InputOnly, CopyFromParent,
910     CWEventMask, &wa);
911   XMapWindow(__glutDisplay, submenu->win);
912   __glutCurrentMenu->num++;
913   submenu->next = __glutCurrentMenu->list;
914   __glutCurrentMenu->list = submenu;
915 }
916 
917 void APIENTRY
glutChangeToMenuEntry(int num,const char * label,int value)918 glutChangeToMenuEntry(int num, const char *label, int value)
919 {
920   GLUTmenuItem *item;
921   int i;
922 
923   if (__glutMappedMenu)
924     menuModificationError();
925   i = __glutCurrentMenu->num;
926   item = __glutCurrentMenu->list;
927   while (item) {
928     if (i == num) {
929       if (item->isTrigger) {
930         /* If changing a submenu trigger to a menu entry, we
931            need to account for submenus.  */
932         item->menu->submenus--;
933       }
934       free(item->label);
935       setMenuItem(item, label, value, False);
936       return;
937     }
938     i--;
939     item = item->next;
940   }
941   __glutWarning("Current menu has no %d item.", num);
942 }
943 
944 void APIENTRY
glutChangeToSubMenu(int num,const char * label,int menu)945 glutChangeToSubMenu(int num, const char *label, int menu)
946 {
947   GLUTmenuItem *item;
948   int i;
949 
950   if (__glutMappedMenu)
951     menuModificationError();
952   i = __glutCurrentMenu->num;
953   item = __glutCurrentMenu->list;
954   while (item) {
955     if (i == num) {
956       if (!item->isTrigger) {
957         /* If changing a menu entry to as submenu trigger, we
958            need to account for submenus.  */
959         item->menu->submenus++;
960       }
961       free(item->label);
962       setMenuItem(item, label, /* base 0 */ menu - 1, True);
963       return;
964     }
965     i--;
966     item = item->next;
967   }
968   __glutWarning("Current menu has no %d item.", num);
969 }
970 
971 void APIENTRY
glutRemoveMenuItem(int num)972 glutRemoveMenuItem(int num)
973 {
974   GLUTmenuItem *item, **prev, *remaining;
975   int pixwidth, i;
976 
977   if (__glutMappedMenu)
978     menuModificationError();
979   i = __glutCurrentMenu->num;
980   prev = &__glutCurrentMenu->list;
981   item = __glutCurrentMenu->list;
982   /* If menu item is removed, the menu's pixwidth may need to
983      be recomputed. */
984   pixwidth = 1;
985   while (item) {
986     if (i == num) {
987       /* If this menu item's pixwidth is as wide as the menu's
988          pixwidth, removing this menu item will necessitate
989          shrinking the menu's pixwidth. */
990       if (item->pixwidth >= __glutCurrentMenu->pixwidth) {
991         /* Continue recalculating menu pixwidth, first skipping
992            the removed item. */
993         remaining = item->next;
994         while (remaining) {
995           if (remaining->pixwidth > pixwidth) {
996             pixwidth = remaining->pixwidth;
997           }
998           remaining = remaining->next;
999         }
1000         __glutCurrentMenu->pixwidth = pixwidth;
1001       }
1002       __glutCurrentMenu->num--;
1003       __glutCurrentMenu->managed = False;
1004 
1005       /* Patch up menu's item list. */
1006       *prev = item->next;
1007 
1008       free(item->label);
1009       free(item);
1010       return;
1011     }
1012     if (item->pixwidth > pixwidth) {
1013       pixwidth = item->pixwidth;
1014     }
1015     i--;
1016     prev = &item->next;
1017     item = item->next;
1018   }
1019   __glutWarning("Current menu has no %d item.", num);
1020 }
1021 
1022 void APIENTRY
glutAttachMenu(int button)1023 glutAttachMenu(int button)
1024 {
1025   if (__glutMappedMenu)
1026     menuModificationError();
1027   if (__glutCurrentWindow->menu[button] < 1) {
1028     __glutCurrentWindow->buttonUses++;
1029   }
1030   __glutChangeWindowEventMask(
1031     ButtonPressMask | ButtonReleaseMask, True);
1032   __glutCurrentWindow->menu[button] = __glutCurrentMenu->id + 1;
1033 }
1034 
1035 void APIENTRY
glutDetachMenu(int button)1036 glutDetachMenu(int button)
1037 {
1038   if (__glutMappedMenu)
1039     menuModificationError();
1040   if (__glutCurrentWindow->menu[button] > 0) {
1041     __glutCurrentWindow->buttonUses--;
1042     __glutChangeWindowEventMask(ButtonPressMask | ButtonReleaseMask,
1043       __glutCurrentWindow->buttonUses > 0);
1044     __glutCurrentWindow->menu[button] = 0;
1045   }
1046 }
1047 /* ENDCENTRY */
1048 
1049 void
__glutMenuItemEnterOrLeave(GLUTmenuItem * item,int num,int type)1050 __glutMenuItemEnterOrLeave(GLUTmenuItem * item,
1051   int num, int type)
1052 {
1053   int alreadyUp = 0;
1054 
1055   if (type == EnterNotify) {
1056     GLUTmenuItem *prevItem = item->menu->highlighted;
1057 
1058     if (prevItem && prevItem != item) {
1059       /* If there's an already higlighted item in this menu
1060          that is different from this one (we could be
1061          re-entering an item with an already cascaded
1062          submenu!), unhighlight the previous item. */
1063       item->menu->highlighted = NULL;
1064       paintMenuItem(prevItem, getMenuItemIndex(prevItem));
1065     }
1066     item->menu->highlighted = item;
1067     __glutItemSelected = item;
1068     if (item->menu->cascade) {
1069       if (!item->isTrigger) {
1070         /* Entered a menu item that is not a submenu trigger,
1071            so pop down the current submenu cascade of this
1072            menu.  */
1073         unmapMenu(item->menu->cascade);
1074         item->menu->cascade = NULL;
1075       } else {
1076         GLUTmenu *submenu = menuList[item->value];
1077 
1078         if (submenu->anchor == item) {
1079           /* We entered the submenu trigger for the submenu
1080              that is already up, so don't take down the
1081              submenu.  */
1082           alreadyUp = 1;
1083         } else {
1084           /* Submenu already popped up for some other submenu
1085              item of this menu; need to pop down that other
1086              submenu cascade.  */
1087           unmapMenu(item->menu->cascade);
1088           item->menu->cascade = NULL;
1089         }
1090       }
1091     }
1092     if (!alreadyUp) {
1093       /* Make sure the menu item gets painted with
1094          highlighting. */
1095       paintMenuItem(item, num);
1096     } else {
1097       /* If already up, should already be highlighted.  */
1098     }
1099   } else {
1100     /* LeaveNotify: Handle leaving a menu item...  */
1101     if (item->menu->cascade &&
1102       item->menu->cascade->anchor == item) {
1103       /* If there is a submenu casacaded from this item, do not
1104          change the highlighting on this item upon leaving. */
1105     } else {
1106       /* Unhighlight this menu item.  */
1107       item->menu->highlighted = NULL;
1108       paintMenuItem(item, num);
1109     }
1110     __glutItemSelected = NULL;
1111   }
1112   if (item->isTrigger) {
1113     if (type == EnterNotify && !alreadyUp) {
1114       GLUTmenu *submenu = menuList[item->value];
1115 
1116       mapMenu(submenu,
1117         item->menu->x + item->menu->pixwidth +
1118         MENU_ARROW_GAP + MENU_ARROW_WIDTH +
1119         MENU_GAP + MENU_BORDER,
1120         item->menu->y + fontHeight * (num - 1) + MENU_GAP);
1121       item->menu->cascade = submenu;
1122       submenu->anchor = item;
1123     }
1124   }
1125 }
1126