d136823c54f0dbc798b2d987a68c8779dd2ae413
[platform/upstream/mesa.git] / src / glut / glx / glut_menu.c
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
29 void (CDECL *__glutMenuStatusFunc) (int, int, int);
30 GLUTmenuItem *__glutItemSelected;
31 GLUTmenu **__glutMenuList = NULL;
32
33 static int menuListSize = 0;
34 static XFontStruct *menuFont = NULL;
35 static Cursor menuCursor;
36 static Colormap menuColormap;
37 static Visual *menuVisual;
38 static int menuDepth;
39 static int fontHeight;
40 static GC blackGC, grayGC, whiteGC;
41 static unsigned long menuBlack, menuWhite, menuGray;
42 static 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.  */
49 static void
50 noFaultXAllocColor(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
101 static int
102 ifSunCreator(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
152 static void
153 menuVisualSetup(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
336 static void
337 menuSetup(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
357 static void
358 menuGraphicsContextSetup(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
375 void
376 __glutSetMenu(GLUTmenu * menu)
377 {
378   __glutCurrentMenu = menu;
379 }
380
381 static void
382 unmapMenu(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
393 static void
394 finishMenu(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
461 static void
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 static void
535 startMenu(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
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
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 static void
610 paintMenu(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 static GLUTmenuItem *
637 getMenuItem(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
671 static int
672 getMenuItemIndex(GLUTmenuItem * item)
673 {
674   int count = 0;
675
676   while (item) {
677     count++;
678     item = item->next;
679   }
680   return count;
681 }
682
683 static GLUTmenu *
684 getMenu(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
698 static GLUTmenu *
699 getMenuByNum(int menunum)
700 {
701   if (menunum < 1 || menunum > menuListSize) {
702     return NULL;
703   }
704   return __glutMenuList[menunum - 1];
705 }
706
707 static int
708 getUnusedMenuSlot(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
736 void
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
745 static void
746 menuItemEnterOrLeave(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. */
827 static void
828 installMenuCallbacks(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
839 int GLUTAPIENTRY 
840 glutCreateMenu(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 */
898 int GLUTAPIENTRY 
899 glutGetMenu(void)
900 {
901   if (__glutCurrentMenu) {
902     return __glutCurrentMenu->id + 1;
903   } else {
904     return 0;
905   }
906 }
907
908 void GLUTAPIENTRY 
909 glutSetMenu(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
926 void
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 */
948 void GLUTAPIENTRY 
949 glutAddMenuEntry(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
976 void GLUTAPIENTRY 
977 glutAddSubMenu(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
1005 void GLUTAPIENTRY 
1006 glutAttachMenu(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 */