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