Fixed off-by-one error in compute_shine_table(). Minor code clean-up
[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 #include <stdlib.h>
13 #include <string.h>
14 #include <stdio.h>
15 #include <errno.h>
16 #include <assert.h>
17
18 #include <unistd.h>
19 #include <X11/Xlib.h>
20 #include <X11/cursorfont.h> /* for XC_arrow */
21
22 #include "glutint.h"
23 #include "layerutil.h"
24
25 void (CDECL *__glutMenuStatusFunc) (int, int, int);
26 GLUTmenuItem *__glutItemSelected;
27 GLUTmenu **__glutMenuList = NULL;
28
29 static int menuListSize = 0;
30 static XFontStruct *menuFont = NULL;
31 static Cursor menuCursor;
32 static Colormap menuColormap;
33 static Visual *menuVisual;
34 static int menuDepth;
35 static int fontHeight;
36 static GC blackGC, grayGC, whiteGC;
37 static unsigned long menuBlack, menuWhite, menuGray;
38 static unsigned long useSaveUnders;
39
40 /* A replacement for XAllocColor (originally by Brian Paul).
41 This function should never fail to allocate a color. When
42 XAllocColor fails, we return the nearest matching color. If
43 we have to allocate many colors this function isn't a great
44 solution; the XQueryColors() could be done just once. */
45 static void
46 noFaultXAllocColor(Display * dpy, Colormap cmap, int cmapSize,
47 XColor * color)
48 {
49 XColor *ctable, subColor;
50 int i, bestmatch;
51 double mindist; /* 3*2^16^2 exceeds 32-bit long int
52 precision. */
53
54 for (;;) {
55 /* First try just using XAllocColor. */
56 if (XAllocColor(dpy, cmap, color)) {
57 return;
58 }
59
60 /* Retrieve color table entries. */
61 /* XXX alloca canidate. */
62 ctable = (XColor *) malloc(cmapSize * sizeof(XColor));
63 for (i = 0; i < cmapSize; i++)
64 ctable[i].pixel = i;
65 XQueryColors(dpy, cmap, ctable, cmapSize);
66
67 /* Find best match. */
68 bestmatch = -1;
69 mindist = 0.0;
70 for (i = 0; i < cmapSize; i++) {
71 double dr = (double) color->red - (double) ctable[i].red;
72 double dg = (double) color->green - (double) ctable[i].green;
73 double db = (double) color->blue - (double) ctable[i].blue;
74 double dist = dr * dr + dg * dg + db * db;
75 if (bestmatch < 0 || dist < mindist) {
76 bestmatch = i;
77 mindist = dist;
78 }
79 }
80
81 /* Return result. */
82 subColor.red = ctable[bestmatch].red;
83 subColor.green = ctable[bestmatch].green;
84 subColor.blue = ctable[bestmatch].blue;
85 free(ctable);
86 if (XAllocColor(dpy, cmap, &subColor)) {
87 *color = subColor;
88 return;
89 }
90 /* Extremely unlikely, but possibly color was deallocated
91 and reallocated by someone else before we could
92 XAllocColor the color cell we located. If so, loop
93 again... */
94 }
95 }
96
97 static int
98 ifSunCreator(void)
99 {
100 char *xvendor, *glvendor, *renderer;
101 int isSunCreator = 0; /* Until proven that it is. */
102 int savedDisplayMode;
103 char *savedDisplayString;
104 GLUTwindow *window;
105
106 #define VENDOR_SUN "Sun Microsystems"
107 #define RENDERER_CREATOR "Creator"
108
109 /* Check the X vendor string first. It is easier to check
110 than the OpenGL vendor and renderer strings since it
111 doesn't require a valid OpenGL rendering context. Bail
112 early if not connected to a Sun. */
113 xvendor = ServerVendor(__glutDisplay);
114 if (!strncmp(xvendor, VENDOR_SUN, sizeof(VENDOR_SUN) - 1)) {
115
116 /* We need a valid current OpenGL rendering context to be
117 able to call glGetString successfully. If there is not
118 a current window, set up a temporary one just to call
119 glGetString with (gag, expensive). */
120 if (__glutCurrentWindow) {
121 window = NULL;
122 } else {
123 savedDisplayMode = __glutDisplayMode;
124 savedDisplayString = __glutDisplayString;
125 __glutDisplayMode = GLUT_RGB | GLUT_SINGLE;
126 __glutDisplayString = NULL;
127 window = __glutCreateWindow(NULL, 0, 0, 1, 1, 0);
128 }
129
130 glvendor = (char *) glGetString(GL_VENDOR);
131 if (!strncmp(glvendor, VENDOR_SUN, sizeof(VENDOR_SUN) - 1)) {
132 renderer = (char *) glGetString(GL_RENDERER);
133 if (!strncmp(renderer, RENDERER_CREATOR, sizeof(RENDERER_CREATOR) - 1)) {
134 isSunCreator = 1;
135 }
136 }
137 /* Destroy the temporary window for glGetString if one
138 needed to be created. */
139 if (window) {
140 __glutDestroyWindow(window, window);
141 __glutDisplayMode = savedDisplayMode;
142 __glutDisplayString = savedDisplayString;
143 }
144 }
145 return isSunCreator;
146 }
147
148 static void
149 menuVisualSetup(void)
150 {
151 XLayerVisualInfo template, *visual, *overlayVisuals;
152 XColor color;
153 Status status;
154 Bool presumablyMesa;
155 int layer, nVisuals, i, dummy;
156 unsigned long *placeHolders = NULL;
157 int numPlaceHolders;
158 Bool allocateHigh;
159
160 allocateHigh = ifSunCreator();
161
162 /* Start with the highest overlay layer and work down. I
163 don't think any hardware has more than 3 overlay layers. */
164 for (layer = 3; layer > 0; layer--) {
165 template.layer = layer;
166 template.vinfo.screen = __glutScreen;
167 overlayVisuals = __glutXGetLayerVisualInfo(__glutDisplay,
168 VisualScreenMask | VisualLayerMask, &template, &nVisuals);
169 if (overlayVisuals) {
170 /* First, check if the default visual is in this layer.
171 If the default visual is in this layer, we try to use
172 it since it has pre-defined black and white pixels and
173
174 using the default visual will probably minimize
175 colormap flashing problems. Suggested by Thomas Roell
176 (thomas@xig.com). */
177 for (i = 0; i < nVisuals; i++) {
178 visual = &overlayVisuals[i];
179 if (visual->vinfo.colormap_size >= 3) {
180 /* Compare visual IDs just to be safe. */
181 if (visual->vinfo.visual->visualid == DefaultVisual(__glutDisplay, __glutScreen)->visualid) {
182 /* Settle for default visual. */
183 menuVisual = DefaultVisual(__glutDisplay, __glutScreen);
184 menuDepth = DefaultDepth(__glutDisplay, __glutScreen);
185 menuColormap = DefaultColormap(__glutDisplay, __glutScreen);
186 menuBlack = BlackPixel(__glutDisplay, __glutScreen);
187 menuWhite = WhitePixel(__glutDisplay, __glutScreen);
188 color.red = color.green = color.blue = 0xaa00;
189 noFaultXAllocColor(__glutDisplay, menuColormap,
190 menuVisual->map_entries, &color);
191 menuGray = color.pixel;
192 useSaveUnders = 0;
193 XFree(overlayVisuals);
194 return;
195 }
196 }
197 }
198 for (i = 0; i < nVisuals; i++) {
199 visual = &overlayVisuals[i];
200 if (visual->vinfo.colormap_size >= 3) {
201 if (allocateHigh) {
202 /* For Sun's Creator graphics, try to force the
203 read-only colors to the high end of the colormap
204 by first allocating read-write place-holder cells
205 for all but the last three cells. This helps
206 avoid colormap flashing problems. */
207 numPlaceHolders = visual->vinfo.colormap_size - 3;
208 if (numPlaceHolders > 0) {
209 placeHolders = (unsigned long *)
210 malloc(numPlaceHolders * sizeof(unsigned long));
211 /* A malloc failure would be harmless. */
212 }
213 }
214 menuColormap = XCreateColormap(__glutDisplay, __glutRoot,
215 visual->vinfo.visual, AllocNone);
216 if (placeHolders) {
217 /* Again for Sun's Creator graphics, do the actual
218 read-write place-holder cell allocation. */
219 status = XAllocColorCells(__glutDisplay, menuColormap, False, 0, 0,
220 placeHolders, numPlaceHolders);
221 if (!status) {
222 XFreeColormap(__glutDisplay, menuColormap);
223 free(placeHolders);
224 continue;
225 }
226 }
227 /* Allocate overlay colormap cells in defined order:
228 gray, black, white to match the IRIS GL allocation
229 scheme. Increases likelihood of less overlay
230 colormap flashing. */
231 /* XXX Nice if these 3 AllocColor's could be done in
232 one protocol round-trip. */
233 color.red = color.green = color.blue = 0xaa00;
234 status = XAllocColor(__glutDisplay,
235 menuColormap, &color);
236 if (!status) {
237 XFreeColormap(__glutDisplay, menuColormap);
238 if (placeHolders) {
239 free(placeHolders);
240 }
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 }
252 continue;
253 }
254 menuBlack = color.pixel;
255 color.red = color.green = color.blue = 0xffff;
256 status = XAllocColor(__glutDisplay,
257 menuColormap, &color);
258 if (!status) {
259 XFreeColormap(__glutDisplay, menuColormap);
260 if (placeHolders) {
261 free(placeHolders);
262 }
263 continue;
264 }
265 if (placeHolders) {
266 /* Now free the placeholder cells. */
267 XFreeColors(__glutDisplay, menuColormap,
268 placeHolders, numPlaceHolders, 0);
269 free(placeHolders);
270 }
271 menuWhite = color.pixel;
272 menuVisual = visual->vinfo.visual;
273 menuDepth = visual->vinfo.depth;
274 /* If using overlays, do not request "save unders". */
275 useSaveUnders = 0;
276 XFree(overlayVisuals);
277 return;
278 }
279 }
280 XFree(overlayVisuals);
281 }
282 }
283 /* Settle for default visual. */
284 menuVisual = DefaultVisual(__glutDisplay, __glutScreen);
285 menuDepth = DefaultDepth(__glutDisplay, __glutScreen);
286 menuColormap = DefaultColormap(__glutDisplay, __glutScreen);
287 menuBlack = BlackPixel(__glutDisplay, __glutScreen);
288 menuWhite = WhitePixel(__glutDisplay, __glutScreen);
289 color.red = color.green = color.blue = 0xaa00;
290 noFaultXAllocColor(__glutDisplay, menuColormap,
291 menuVisual->map_entries, &color);
292 menuGray = color.pixel;
293
294 /* When no overlays are supported, we would like to use X
295 "save unders" to avoid exposes to windows obscured by
296 pop-up menus. However, OpenGL's direct rendering support
297 means OpenGL interacts poorly with X backing store and
298 save unders. X servers do not (in implementation
299 practice) redirect OpenGL rendering destined to obscured
300 window regions into backing store.
301
302 Implementation solutions exist for this problem, but they
303 are expensive and high-end OpenGL implementations
304 typically provide fast rendering and/or overlays to
305 obviate the problem associated of user interfaces (pop-up
306 menus) forcing redraws of complex normal plane scenes.
307 (See support for overlays pop-up menus above.)
308
309 Mesa 3D, however, does not support direct rendering.
310 Overlays are often unavailable to Mesa, and Mesa is also
311 relatively slow. For these reasons, Mesa-rendering GLUT
312 programs can and should use X save unders.
313
314 Look for the GLX extension. If _not_ supported, we are
315 presumably using Mesa so enable save unders. */
316
317 presumablyMesa = !XQueryExtension(__glutDisplay, "GLX",
318 &dummy, &dummy, &dummy);
319
320 if (presumablyMesa) {
321 useSaveUnders = CWSaveUnder;
322 } else {
323 useSaveUnders = 0;
324 }
325 }
326
327 static void
328 menuSetup(void)
329 {
330 if (menuFont) {
331 /* MenuFont overload to indicate menu initalization. */
332 return;
333 }
334 menuFont = XLoadQueryFont(__glutDisplay,
335 "-*-helvetica-bold-o-normal--14-*-*-*-p-*-iso8859-1");
336 if (!menuFont) {
337 /* Try back up font. */
338 menuFont = XLoadQueryFont(__glutDisplay, "fixed");
339 }
340 if (!menuFont) {
341 __glutFatalError("could not load font.");
342 }
343 menuVisualSetup();
344 fontHeight = menuFont->ascent + menuFont->descent;
345 menuCursor = XCreateFontCursor(__glutDisplay, XC_arrow);
346 }
347
348 static void
349 menuGraphicsContextSetup(Window win)
350 {
351 XGCValues gcvals;
352
353 if (blackGC != None) {
354 return;
355 }
356 gcvals.font = menuFont->fid;
357 gcvals.foreground = menuBlack;
358 blackGC = XCreateGC(__glutDisplay, win,
359 GCFont | GCForeground, &gcvals);
360 gcvals.foreground = menuGray;
361 grayGC = XCreateGC(__glutDisplay, win, GCForeground, &gcvals);
362 gcvals.foreground = menuWhite;
363 whiteGC = XCreateGC(__glutDisplay, win, GCForeground, &gcvals);
364 }
365
366 void
367 __glutSetMenu(GLUTmenu * menu)
368 {
369 __glutCurrentMenu = menu;
370 }
371
372 static void
373 unmapMenu(GLUTmenu * menu)
374 {
375 if (menu->cascade) {
376 unmapMenu(menu->cascade);
377 menu->cascade = NULL;
378 }
379 menu->anchor = NULL;
380 menu->highlighted = NULL;
381 XUnmapWindow(__glutDisplay, menu->win);
382 }
383
384 static void
385 finishMenu(Window win, int x, int y)
386 {
387 Window dummy;
388 int rc;
389
390 unmapMenu(__glutMappedMenu);
391 XUngrabPointer(__glutDisplay, CurrentTime);
392
393 /* Popping up an overlay popup menu will install its own
394 colormap. If the window associated with the menu has an
395 overlay, install that window's overlay colormap so the
396 overlay isn't left using the popup menu's colormap. */
397 if (__glutMenuWindow->overlay) {
398 XInstallColormap(__glutDisplay,
399 __glutMenuWindow->overlay->colormap->cmap);
400 }
401
402 /* This XFlush is needed to to make sure the pointer is
403 really ungrabbed when the application's menu callback is
404 called. Otherwise, a deadlock might happen because the
405 application may try to read from an terminal window, but
406 yet the ungrab hasn't really happened since it hasn't been
407 flushed out. */
408 XFlush(__glutDisplay);
409
410 if (__glutMenuStatusFunc) {
411 if (win != __glutMenuWindow->win) {
412 /* The button release may have occurred in a window other
413 than the window requesting the pop-up menu (for
414 example, one of the submenu windows). In this case, we
415 need to translate the coordinates into the coordinate
416 system of the window associated with the window. */
417 rc = XTranslateCoordinates(__glutDisplay, win, __glutMenuWindow->win,
418 x, y, &x, &y, &dummy);
419 assert(rc != False); /* Will always be on same screen. */
420 }
421 __glutSetWindow(__glutMenuWindow);
422 __glutSetMenu(__glutMappedMenu);
423
424 /* Setting __glutMappedMenu to NULL permits operations that
425 change menus or destroy the menu window again. */
426 __glutMappedMenu = NULL;
427
428 __glutMenuStatusFunc(GLUT_MENU_NOT_IN_USE, x, y);
429 }
430 /* Setting __glutMappedMenu to NULL permits operations that
431 change menus or destroy the menu window again. */
432 __glutMappedMenu = NULL;
433
434 /* If an item is selected and it is not a submenu trigger,
435 generate menu callback. */
436 if (__glutItemSelected && !__glutItemSelected->isTrigger) {
437 __glutSetWindow(__glutMenuWindow);
438 /* When menu callback is triggered, current menu should be
439 set to the callback menu. */
440 __glutSetMenu(__glutItemSelected->menu);
441 __glutItemSelected->menu->select(
442 __glutItemSelected->value);
443 }
444 __glutMenuWindow = NULL;
445 }
446
447 #define MENU_BORDER 1
448 #define MENU_GAP 2
449 #define MENU_ARROW_GAP 6
450 #define MENU_ARROW_WIDTH 8
451
452 static void
453 mapMenu(GLUTmenu * menu, int x, int y)
454 {
455 XWindowChanges changes;
456 unsigned int mask;
457 int subMenuExtension, num;
458
459 /* If there are submenus, we need to provide extra space for
460 the submenu pull arrow. */
461 if (menu->submenus > 0) {
462 subMenuExtension = MENU_ARROW_GAP + MENU_ARROW_WIDTH;
463 } else {
464 subMenuExtension = 0;
465 }
466
467 changes.stack_mode = Above;
468 mask = CWStackMode | CWX | CWY;
469 /* If the menu isn't managed (ie, validated so all the
470 InputOnly subwindows are the right size), do so. */
471 if (!menu->managed) {
472 GLUTmenuItem *item;
473
474 item = menu->list;
475 num = menu->num;
476 while (item) {
477 XWindowChanges itemupdate;
478
479 itemupdate.y = (num - 1) * fontHeight + MENU_GAP;
480 itemupdate.width = menu->pixwidth;
481 itemupdate.width += subMenuExtension;
482 XConfigureWindow(__glutDisplay, item->win,
483 CWWidth | CWY, &itemupdate);
484 item = item->next;
485 num--;
486 }
487 menu->pixheight = MENU_GAP +
488 fontHeight * menu->num + MENU_GAP;
489 changes.height = menu->pixheight;
490 changes.width = MENU_GAP +
491 menu->pixwidth + subMenuExtension + MENU_GAP;
492 mask |= CWWidth | CWHeight;
493 menu->managed = True;
494 }
495 /* Make sure menu appears fully on screen. */
496 if (y + menu->pixheight >= __glutScreenHeight) {
497 changes.y = __glutScreenHeight - menu->pixheight;
498 } else {
499 changes.y = y;
500 }
501 if (x + menu->pixwidth + subMenuExtension >=
502 __glutScreenWidth) {
503 changes.x = __glutScreenWidth -
504 menu->pixwidth + subMenuExtension;
505 } else {
506 changes.x = x;
507 }
508
509 /* Rember where the menu is placed so submenus can be
510 properly placed relative to it. */
511 menu->x = changes.x;
512 menu->y = changes.y;
513
514 XConfigureWindow(__glutDisplay, menu->win, mask, &changes);
515 XInstallColormap(__glutDisplay, menuColormap);
516 /* XXX The XRaiseWindow below should not be necessary because
517 the XConfigureWindow requests an Above stack mode (same as
518 XRaiseWindow), but some Sun users complained this was still
519 necessary. Probably some window manager or X server bug on
520 these machines?? */
521 XRaiseWindow(__glutDisplay, menu->win);
522 XMapWindow(__glutDisplay, menu->win);
523 }
524
525 static void
526 startMenu(GLUTmenu * menu, GLUTwindow * window,
527 int x, int y, int x_win, int y_win)
528 {
529 int grab;
530
531 assert(__glutMappedMenu == NULL);
532 grab = XGrabPointer(__glutDisplay, __glutRoot, True,
533 ButtonPressMask | ButtonReleaseMask,
534 GrabModeAsync, GrabModeAsync,
535 __glutRoot, menuCursor, CurrentTime);
536 if (grab != GrabSuccess) {
537 /* Somebody else has pointer grabbed, ignore menu
538 activation. */
539 return;
540 }
541 __glutMappedMenu = menu;
542 __glutMenuWindow = window;
543 __glutItemSelected = NULL;
544 if (__glutMenuStatusFunc) {
545 __glutSetMenu(menu);
546 __glutSetWindow(window);
547 __glutMenuStatusFunc(GLUT_MENU_IN_USE, x_win, y_win);
548 }
549 mapMenu(menu, x, y);
550 }
551
552 static void
553 paintSubMenuArrow(Window win, int x, int y)
554 {
555 XPoint p[5];
556
557 p[0].x = p[4].x = x;
558 p[0].y = p[4].y = y - menuFont->ascent + 1;
559 p[1].x = p[0].x + MENU_ARROW_WIDTH - 1;
560 p[1].y = p[0].y + (menuFont->ascent / 2) - 1;
561 p[2].x = p[1].x;
562 p[2].y = p[1].y + 1;
563 p[3].x = p[0].x;
564 p[3].y = p[0].y + menuFont->ascent - 2;
565 XFillPolygon(__glutDisplay, win,
566 whiteGC, p, 4, Convex, CoordModeOrigin);
567 XDrawLines(__glutDisplay, win, blackGC, p, 5, CoordModeOrigin);
568 }
569
570 static void
571 paintMenuItem(GLUTmenuItem * item, int num)
572 {
573 Window win = item->menu->win;
574 GC gc;
575 int y;
576 int subMenuExtension;
577
578 if (item->menu->submenus > 0) {
579 subMenuExtension = MENU_ARROW_GAP + MENU_ARROW_WIDTH;
580 } else {
581 subMenuExtension = 0;
582 }
583 if (item->menu->highlighted == item) {
584 gc = whiteGC;
585 } else {
586 gc = grayGC;
587 }
588 y = MENU_GAP + fontHeight * num - menuFont->descent;
589 XFillRectangle(__glutDisplay, win, gc,
590 MENU_GAP, y - fontHeight + menuFont->descent,
591 item->menu->pixwidth + subMenuExtension, fontHeight);
592 XDrawString(__glutDisplay, win, blackGC,
593 MENU_GAP, y, item->label, item->len);
594 if (item->isTrigger) {
595 paintSubMenuArrow(win,
596 item->menu->pixwidth + MENU_ARROW_GAP + 1, y);
597 }
598 }
599
600 static void
601 paintMenu(GLUTmenu * menu)
602 {
603 GLUTmenuItem *item;
604 int i = menu->num;
605 int y = MENU_GAP + fontHeight * i - menuFont->descent;
606
607 item = menu->list;
608 while (item) {
609 if (item->menu->highlighted == item) {
610 paintMenuItem(item, i);
611 } else {
612 /* Quick render of the menu item; assume background
613 already cleared to gray. */
614 XDrawString(__glutDisplay, menu->win, blackGC,
615 2, y, item->label, item->len);
616 if (item->isTrigger) {
617 paintSubMenuArrow(menu->win,
618 menu->pixwidth + MENU_ARROW_GAP + 1, y);
619 }
620 }
621 i--;
622 y -= fontHeight;
623 item = item->next;
624 }
625 }
626
627 static GLUTmenuItem *
628 getMenuItem(GLUTmenu * menu, Window win, int *which)
629 {
630 GLUTmenuItem *item;
631 int i;
632
633 if (menu->searched) {
634 __glutFatalError("submenu infinite loop detected");
635 }
636 menu->searched = True;
637 i = menu->num;
638 item = menu->list;
639 while (item) {
640 if (item->win == win) {
641 *which = i;
642 menu->searched = False;
643 return item;
644 }
645 if (item->isTrigger) {
646 GLUTmenuItem *subitem;
647
648 subitem = __glutGetMenuItem(__glutMenuList[item->value],
649 win, which);
650 if (subitem) {
651 menu->searched = False;
652 return subitem;
653 }
654 }
655 i--;
656 item = item->next;
657 }
658 menu->searched = False;
659 return NULL;
660 }
661
662 static int
663 getMenuItemIndex(GLUTmenuItem * item)
664 {
665 int count = 0;
666
667 while (item) {
668 count++;
669 item = item->next;
670 }
671 return count;
672 }
673
674 static GLUTmenu *
675 getMenu(Window win)
676 {
677 GLUTmenu *menu;
678
679 menu = __glutMappedMenu;
680 while (menu) {
681 if (win == menu->win) {
682 return menu;
683 }
684 menu = menu->cascade;
685 }
686 return NULL;
687 }
688
689 static GLUTmenu *
690 getMenuByNum(int menunum)
691 {
692 if (menunum < 1 || menunum > menuListSize) {
693 return NULL;
694 }
695 return __glutMenuList[menunum - 1];
696 }
697
698 static int
699 getUnusedMenuSlot(void)
700 {
701 int i;
702
703 /* Look for allocated, unused slot. */
704 for (i = 0; i < menuListSize; i++) {
705 if (!__glutMenuList[i]) {
706 return i;
707 }
708 }
709 /* Allocate a new slot. */
710 menuListSize++;
711 if (__glutMenuList) {
712 __glutMenuList = (GLUTmenu **)
713 realloc(__glutMenuList, menuListSize * sizeof(GLUTmenu *));
714 } else {
715 /* XXX Some realloc's do not correctly perform a malloc
716 when asked to perform a realloc on a NULL pointer,
717 though the ANSI C library spec requires this. */
718 __glutMenuList = (GLUTmenu **) malloc(sizeof(GLUTmenu *));
719 }
720 if (!__glutMenuList) {
721 __glutFatalError("out of memory.");
722 }
723 __glutMenuList[menuListSize - 1] = NULL;
724 return menuListSize - 1;
725 }
726
727 void
728 __glutMenuModificationError(void)
729 {
730 /* XXX Remove the warning after GLUT 3.0. */
731 __glutWarning("The following is a new check for GLUT 3.0; update your code.");
732 __glutFatalError("menu manipulation not allowed while menus in use.");
733 }
734
735
736 static void
737 menuItemEnterOrLeave(GLUTmenuItem * item,
738 int num, int type)
739 {
740 int alreadyUp = 0;
741
742 if (type == EnterNotify) {
743 GLUTmenuItem *prevItem = item->menu->highlighted;
744
745 if (prevItem && prevItem != item) {
746 /* If there's an already higlighted item in this menu
747 that is different from this one (we could be
748 re-entering an item with an already cascaded
749 submenu!), unhighlight the previous item. */
750 item->menu->highlighted = NULL;
751 paintMenuItem(prevItem, getMenuItemIndex(prevItem));
752 }
753 item->menu->highlighted = item;
754 __glutItemSelected = item;
755 if (item->menu->cascade) {
756 if (!item->isTrigger) {
757 /* Entered a menu item that is not a submenu trigger,
758 so pop down the current submenu cascade of this
759 menu. */
760 unmapMenu(item->menu->cascade);
761 item->menu->cascade = NULL;
762 } else {
763 GLUTmenu *submenu = __glutMenuList[item->value];
764
765 if (submenu->anchor == item) {
766 /* We entered the submenu trigger for the submenu
767 that is already up, so don't take down the
768 submenu. */
769 alreadyUp = 1;
770 } else {
771 /* Submenu already popped up for some other submenu
772 item of this menu; need to pop down that other
773 submenu cascade. */
774 unmapMenu(item->menu->cascade);
775 item->menu->cascade = NULL;
776 }
777 }
778 }
779 if (!alreadyUp) {
780 /* Make sure the menu item gets painted with
781 highlighting. */
782 paintMenuItem(item, num);
783 } else {
784 /* If already up, should already be highlighted. */
785 }
786 } else {
787 /* LeaveNotify: Handle leaving a menu item... */
788 if (item->menu->cascade &&
789 item->menu->cascade->anchor == item) {
790 /* If there is a submenu casacaded from this item, do not
791 change the highlighting on this item upon leaving. */
792 } else {
793 /* Unhighlight this menu item. */
794 item->menu->highlighted = NULL;
795 paintMenuItem(item, num);
796 }
797 __glutItemSelected = NULL;
798 }
799 if (item->isTrigger) {
800 if (type == EnterNotify && !alreadyUp) {
801 GLUTmenu *submenu = __glutMenuList[item->value];
802
803 mapMenu(submenu,
804 item->menu->x + item->menu->pixwidth +
805 MENU_ARROW_GAP + MENU_ARROW_WIDTH +
806 MENU_GAP + MENU_BORDER,
807 item->menu->y + fontHeight * (num - 1) + MENU_GAP);
808 item->menu->cascade = submenu;
809 submenu->anchor = item;
810 }
811 }
812 }
813
814 /* Installs callback functions for use by glut_event.c The point
815 of this is so that GLUT's menu code only gets linked into
816 GLUT binaries (assuming a static library) if the GLUT menu
817 API is used. */
818 static void
819 installMenuCallbacks(void)
820 {
821 __glutMenuItemEnterOrLeave = menuItemEnterOrLeave;
822 __glutFinishMenu = finishMenu;
823 __glutPaintMenu = paintMenu;
824 __glutStartMenu = startMenu;
825 __glutGetMenuByNum = getMenuByNum;
826 __glutGetMenu = getMenu;
827 __glutGetMenuItem = getMenuItem;
828 }
829
830 int APIENTRY
831 glutCreateMenu(GLUTselectCB selectFunc)
832 {
833 XSetWindowAttributes wa;
834 GLUTmenu *menu;
835 int menuid;
836
837 if (__glutMappedMenu) {
838 __glutMenuModificationError();
839 }
840 if (!__glutDisplay) {
841 __glutOpenXConnection(NULL);
842 }
843
844 installMenuCallbacks();
845
846 menuid = getUnusedMenuSlot();
847 menu = (GLUTmenu *) malloc(sizeof(GLUTmenu));
848 if (!menu) {
849 __glutFatalError("out of memory.");
850 }
851 menu->id = menuid;
852 menu->num = 0;
853 menu->submenus = 0;
854 menu->managed = False;
855 menu->searched = False;
856 menu->pixwidth = 0;
857 menu->select = selectFunc;
858 menu->list = NULL;
859 menu->cascade = NULL;
860 menu->highlighted = NULL;
861 menu->anchor = NULL;
862 menuSetup();
863 wa.override_redirect = True;
864 wa.background_pixel = menuGray;
865 wa.border_pixel = menuBlack;
866 wa.colormap = menuColormap;
867 wa.event_mask = StructureNotifyMask | ExposureMask |
868 ButtonPressMask | ButtonReleaseMask |
869 EnterWindowMask | LeaveWindowMask;
870 /* Save unders really only enabled if useSaveUnders is set to
871 CWSaveUnder, ie. using Mesa 3D. See earlier comments. */
872 wa.save_under = True;
873 menu->win = XCreateWindow(__glutDisplay, __glutRoot,
874 /* Real position determined when mapped. */
875 0, 0,
876 /* Real size will be determined when menu is manged. */
877 1, 1,
878 MENU_BORDER, menuDepth, InputOutput, menuVisual,
879 CWOverrideRedirect | CWBackPixel | CWBorderPixel |
880 CWEventMask | CWColormap | useSaveUnders,
881 &wa);
882 menuGraphicsContextSetup(menu->win);
883 __glutMenuList[menuid] = menu;
884 __glutSetMenu(menu);
885 return menuid + 1;
886 }
887
888 /* CENTRY */
889 int APIENTRY
890 glutGetMenu(void)
891 {
892 if (__glutCurrentMenu) {
893 return __glutCurrentMenu->id + 1;
894 } else {
895 return 0;
896 }
897 }
898
899 void APIENTRY
900 glutSetMenu(int menuid)
901 {
902 GLUTmenu *menu;
903
904 if (menuid < 1 || menuid > menuListSize) {
905 __glutWarning("glutSetMenu attempted on bogus menu.");
906 return;
907 }
908 menu = __glutMenuList[menuid - 1];
909 if (!menu) {
910 __glutWarning("glutSetMenu attempted on bogus menu.");
911 return;
912 }
913 __glutSetMenu(menu);
914 }
915 /* ENDCENTRY */
916
917 void
918 __glutSetMenuItem(GLUTmenuItem * item, const char *label,
919 int value, Bool isTrigger)
920 {
921 GLUTmenu *menu;
922
923 menu = item->menu;
924 item->label = __glutStrdup(label);
925 if (!item->label) {
926 __glutFatalError("out of memory.");
927 }
928 item->isTrigger = isTrigger;
929 item->len = (int) strlen(label);
930 item->value = value;
931 item->pixwidth = XTextWidth(menuFont, label, item->len) + 4;
932 if (item->pixwidth > menu->pixwidth) {
933 menu->pixwidth = item->pixwidth;
934 }
935 menu->managed = False;
936 }
937
938 /* CENTRY */
939 void APIENTRY
940 glutAddMenuEntry(const char *label, int value)
941 {
942 XSetWindowAttributes wa;
943 GLUTmenuItem *entry;
944
945 if (__glutMappedMenu) {
946 __glutMenuModificationError();
947 }
948 entry = (GLUTmenuItem *) malloc(sizeof(GLUTmenuItem));
949 if (!entry) {
950 __glutFatalError("out of memory.");
951 }
952 entry->menu = __glutCurrentMenu;
953 __glutSetMenuItem(entry, label, value, False);
954 wa.event_mask = EnterWindowMask | LeaveWindowMask;
955 entry->win = XCreateWindow(__glutDisplay,
956 __glutCurrentMenu->win, MENU_GAP,
957 __glutCurrentMenu->num * fontHeight + MENU_GAP, /* x & y */
958 entry->pixwidth, fontHeight, /* width & height */
959 0, CopyFromParent, InputOnly, CopyFromParent,
960 CWEventMask, &wa);
961 XMapWindow(__glutDisplay, entry->win);
962 __glutCurrentMenu->num++;
963 entry->next = __glutCurrentMenu->list;
964 __glutCurrentMenu->list = entry;
965 }
966
967 void APIENTRY
968 glutAddSubMenu(const char *label, int menu)
969 {
970 XSetWindowAttributes wa;
971 GLUTmenuItem *submenu;
972
973 if (__glutMappedMenu) {
974 __glutMenuModificationError();
975 }
976 submenu = (GLUTmenuItem *) malloc(sizeof(GLUTmenuItem));
977 if (!submenu) {
978 __glutFatalError("out of memory.");
979 }
980 __glutCurrentMenu->submenus++;
981 submenu->menu = __glutCurrentMenu;
982 __glutSetMenuItem(submenu, label, /* base 0 */ menu - 1, True);
983 wa.event_mask = EnterWindowMask | LeaveWindowMask;
984 submenu->win = XCreateWindow(__glutDisplay,
985 __glutCurrentMenu->win, MENU_GAP,
986 __glutCurrentMenu->num * fontHeight + MENU_GAP, /* x & y */
987 submenu->pixwidth, fontHeight, /* width & height */
988 0, CopyFromParent, InputOnly, CopyFromParent,
989 CWEventMask, &wa);
990 XMapWindow(__glutDisplay, submenu->win);
991 __glutCurrentMenu->num++;
992 submenu->next = __glutCurrentMenu->list;
993 __glutCurrentMenu->list = submenu;
994 }
995
996 void APIENTRY
997 glutAttachMenu(int button)
998 {
999 if (__glutMappedMenu) {
1000 __glutMenuModificationError();
1001 }
1002 installMenuCallbacks();
1003 if (__glutCurrentWindow->menu[button] < 1) {
1004 __glutCurrentWindow->buttonUses++;
1005 }
1006 __glutChangeWindowEventMask(
1007 ButtonPressMask | ButtonReleaseMask, True);
1008 __glutCurrentWindow->menu[button] = __glutCurrentMenu->id + 1;
1009 }
1010 /* ENDCENTRY */