Merge commit '381d5e209815235911c4aab516037c868c8f695f'
[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 */