Committing in .
[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;
107 char *savedDisplayString;
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;
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 continue;
229 }
230 }
231 /* Allocate overlay colormap cells in defined order:
232 gray, black, white to match the IRIS GL allocation
233 scheme. Increases likelihood of less overlay
234 colormap flashing. */
235 /* XXX Nice if these 3 AllocColor's could be done in
236 one protocol round-trip. */
237 color.red = color.green = color.blue = 0xaa00;
238 status = XAllocColor(__glutDisplay,
239 menuColormap, &color);
240 if (!status) {
241 XFreeColormap(__glutDisplay, menuColormap);
242 if (placeHolders) {
243 free(placeHolders);
244 }
245 continue;
246 }
247 menuGray = color.pixel;
248 color.red = color.green = color.blue = 0x0000;
249 status = XAllocColor(__glutDisplay,
250 menuColormap, &color);
251 if (!status) {
252 XFreeColormap(__glutDisplay, menuColormap);
253 if (placeHolders) {
254 free(placeHolders);
255 }
256 continue;
257 }
258 menuBlack = color.pixel;
259 color.red = color.green = color.blue = 0xffff;
260 status = XAllocColor(__glutDisplay,
261 menuColormap, &color);
262 if (!status) {
263 XFreeColormap(__glutDisplay, menuColormap);
264 if (placeHolders) {
265 free(placeHolders);
266 }
267 continue;
268 }
269 if (placeHolders) {
270 /* Now free the placeholder cells. */
271 XFreeColors(__glutDisplay, menuColormap,
272 placeHolders, numPlaceHolders, 0);
273 free(placeHolders);
274 }
275 menuWhite = color.pixel;
276 menuVisual = visual->vinfo.visual;
277 menuDepth = visual->vinfo.depth;
278 /* If using overlays, do not request "save unders". */
279 useSaveUnders = 0;
280 XFree(overlayVisuals);
281 return;
282 }
283 }
284 XFree(overlayVisuals);
285 }
286 }
287 /* Settle for default visual. */
288 menuVisual = DefaultVisual(__glutDisplay, __glutScreen);
289 menuDepth = DefaultDepth(__glutDisplay, __glutScreen);
290 menuColormap = DefaultColormap(__glutDisplay, __glutScreen);
291 menuBlack = BlackPixel(__glutDisplay, __glutScreen);
292 menuWhite = WhitePixel(__glutDisplay, __glutScreen);
293 color.red = color.green = color.blue = 0xaa00;
294 noFaultXAllocColor(__glutDisplay, menuColormap,
295 menuVisual->map_entries, &color);
296 menuGray = color.pixel;
297
298 /* When no overlays are supported, we would like to use X
299 "save unders" to avoid exposes to windows obscured by
300 pop-up menus. However, OpenGL's direct rendering support
301 means OpenGL interacts poorly with X backing store and
302 save unders. X servers do not (in implementation
303 practice) redirect OpenGL rendering destined to obscured
304 window regions into backing store.
305
306 Implementation solutions exist for this problem, but they
307 are expensive and high-end OpenGL implementations
308 typically provide fast rendering and/or overlays to
309 obviate the problem associated of user interfaces (pop-up
310 menus) forcing redraws of complex normal plane scenes.
311 (See support for overlays pop-up menus above.)
312
313 Mesa 3D, however, does not support direct rendering.
314 Overlays are often unavailable to Mesa, and Mesa is also
315 relatively slow. For these reasons, Mesa-rendering GLUT
316 programs can and should use X save unders.
317
318 Look for the GLX extension. If _not_ supported, we are
319 presumably using Mesa so enable save unders. */
320
321 presumablyMesa = !XQueryExtension(__glutDisplay, "GLX",
322 &dummy, &dummy, &dummy);
323
324 if (presumablyMesa) {
325 useSaveUnders = CWSaveUnder;
326 } else {
327 useSaveUnders = 0;
328 }
329 }
330
331 static void
332 menuSetup(void)
333 {
334 if (menuFont) {
335 /* MenuFont overload to indicate menu initalization. */
336 return;
337 }
338 menuFont = XLoadQueryFont(__glutDisplay,
339 "-*-helvetica-bold-o-normal--14-*-*-*-p-*-iso8859-1");
340 if (!menuFont) {
341 /* Try back up font. */
342 menuFont = XLoadQueryFont(__glutDisplay, "fixed");
343 }
344 if (!menuFont) {
345 __glutFatalError("could not load font.");
346 }
347 menuVisualSetup();
348 fontHeight = menuFont->ascent + menuFont->descent;
349 menuCursor = XCreateFontCursor(__glutDisplay, XC_arrow);
350 }
351
352 static void
353 menuGraphicsContextSetup(Window win)
354 {
355 XGCValues gcvals;
356
357 if (blackGC != None) {
358 return;
359 }
360 gcvals.font = menuFont->fid;
361 gcvals.foreground = menuBlack;
362 blackGC = XCreateGC(__glutDisplay, win,
363 GCFont | GCForeground, &gcvals);
364 gcvals.foreground = menuGray;
365 grayGC = XCreateGC(__glutDisplay, win, GCForeground, &gcvals);
366 gcvals.foreground = menuWhite;
367 whiteGC = XCreateGC(__glutDisplay, win, GCForeground, &gcvals);
368 }
369
370 void
371 __glutSetMenu(GLUTmenu * menu)
372 {
373 __glutCurrentMenu = menu;
374 }
375
376 static void
377 unmapMenu(GLUTmenu * menu)
378 {
379 if (menu->cascade) {
380 unmapMenu(menu->cascade);
381 menu->cascade = NULL;
382 }
383 menu->anchor = NULL;
384 menu->highlighted = NULL;
385 XUnmapWindow(__glutDisplay, menu->win);
386 }
387
388 static void
389 finishMenu(Window win, int x, int y)
390 {
391 Window dummy;
392 int rc;
393
394 unmapMenu(__glutMappedMenu);
395 XUngrabPointer(__glutDisplay, CurrentTime);
396
397 /* Popping up an overlay popup menu will install its own
398 colormap. If the window associated with the menu has an
399 overlay, install that window's overlay colormap so the
400 overlay isn't left using the popup menu's colormap. */
401 if (__glutMenuWindow->overlay) {
402 XInstallColormap(__glutDisplay,
403 __glutMenuWindow->overlay->colormap->cmap);
404 }
405
406 /* This XFlush is needed to to make sure the pointer is
407 really ungrabbed when the application's menu callback is
408 called. Otherwise, a deadlock might happen because the
409 application may try to read from an terminal window, but
410 yet the ungrab hasn't really happened since it hasn't been
411 flushed out. */
412 XFlush(__glutDisplay);
413
414 if (__glutMenuStatusFunc) {
415 if (win != __glutMenuWindow->win) {
416 /* The button release may have occurred in a window other
417 than the window requesting the pop-up menu (for
418 example, one of the submenu windows). In this case, we
419 need to translate the coordinates into the coordinate
420 system of the window associated with the window. */
421 rc = XTranslateCoordinates(__glutDisplay, win, __glutMenuWindow->win,
422 x, y, &x, &y, &dummy);
423 assert(rc != False); /* Will always be on same screen. */
424 }
425 __glutSetWindow(__glutMenuWindow);
426 __glutSetMenu(__glutMappedMenu);
427
428 /* Setting __glutMappedMenu to NULL permits operations that
429 change menus or destroy the menu window again. */
430 __glutMappedMenu = NULL;
431
432 __glutMenuStatusFunc(GLUT_MENU_NOT_IN_USE, x, y);
433 }
434 /* Setting __glutMappedMenu to NULL permits operations that
435 change menus or destroy the menu window again. */
436 __glutMappedMenu = NULL;
437
438 /* If an item is selected and it is not a submenu trigger,
439 generate menu callback. */
440 if (__glutItemSelected && !__glutItemSelected->isTrigger) {
441 __glutSetWindow(__glutMenuWindow);
442 /* When menu callback is triggered, current menu should be
443 set to the callback menu. */
444 __glutSetMenu(__glutItemSelected->menu);
445 __glutItemSelected->menu->select(
446 __glutItemSelected->value);
447 }
448 __glutMenuWindow = NULL;
449 }
450
451 #define MENU_BORDER 1
452 #define MENU_GAP 2
453 #define MENU_ARROW_GAP 6
454 #define MENU_ARROW_WIDTH 8
455
456 static void
457 mapMenu(GLUTmenu * menu, int x, int y)
458 {
459 XWindowChanges changes;
460 unsigned int mask;
461 int subMenuExtension, num;
462
463 /* If there are submenus, we need to provide extra space for
464 the submenu pull arrow. */
465 if (menu->submenus > 0) {
466 subMenuExtension = MENU_ARROW_GAP + MENU_ARROW_WIDTH;
467 } else {
468 subMenuExtension = 0;
469 }
470
471 changes.stack_mode = Above;
472 mask = CWStackMode | CWX | CWY;
473 /* If the menu isn't managed (ie, validated so all the
474 InputOnly subwindows are the right size), do so. */
475 if (!menu->managed) {
476 GLUTmenuItem *item;
477
478 item = menu->list;
479 num = menu->num;
480 while (item) {
481 XWindowChanges itemupdate;
482
483 itemupdate.y = (num - 1) * fontHeight + MENU_GAP;
484 itemupdate.width = menu->pixwidth;
485 itemupdate.width += subMenuExtension;
486 XConfigureWindow(__glutDisplay, item->win,
487 CWWidth | CWY, &itemupdate);
488 item = item->next;
489 num--;
490 }
491 menu->pixheight = MENU_GAP +
492 fontHeight * menu->num + MENU_GAP;
493 changes.height = menu->pixheight;
494 changes.width = MENU_GAP +
495 menu->pixwidth + subMenuExtension + MENU_GAP;
496 mask |= CWWidth | CWHeight;
497 menu->managed = True;
498 }
499 /* Make sure menu appears fully on screen. */
500 if (y + menu->pixheight >= __glutScreenHeight) {
501 changes.y = __glutScreenHeight - menu->pixheight;
502 } else {
503 changes.y = y;
504 }
505 if (x + menu->pixwidth + subMenuExtension >=
506 __glutScreenWidth) {
507 changes.x = __glutScreenWidth -
508 menu->pixwidth + subMenuExtension;
509 } else {
510 changes.x = x;
511 }
512
513 /* Rember where the menu is placed so submenus can be
514 properly placed relative to it. */
515 menu->x = changes.x;
516 menu->y = changes.y;
517
518 XConfigureWindow(__glutDisplay, menu->win, mask, &changes);
519 XInstallColormap(__glutDisplay, menuColormap);
520 /* XXX The XRaiseWindow below should not be necessary because
521 the XConfigureWindow requests an Above stack mode (same as
522 XRaiseWindow), but some Sun users complained this was still
523 necessary. Probably some window manager or X server bug on
524 these machines?? */
525 XRaiseWindow(__glutDisplay, menu->win);
526 XMapWindow(__glutDisplay, menu->win);
527 }
528
529 static void
530 startMenu(GLUTmenu * menu, GLUTwindow * window,
531 int x, int y, int x_win, int y_win)
532 {
533 int grab;
534
535 assert(__glutMappedMenu == NULL);
536 grab = XGrabPointer(__glutDisplay, __glutRoot, True,
537 ButtonPressMask | ButtonReleaseMask,
538 GrabModeAsync, GrabModeAsync,
539 __glutRoot, menuCursor, CurrentTime);
540 if (grab != GrabSuccess) {
541 /* Somebody else has pointer grabbed, ignore menu
542 activation. */
543 return;
544 }
545 __glutMappedMenu = menu;
546 __glutMenuWindow = window;
547 __glutItemSelected = NULL;
548 if (__glutMenuStatusFunc) {
549 __glutSetMenu(menu);
550 __glutSetWindow(window);
551 __glutMenuStatusFunc(GLUT_MENU_IN_USE, x_win, y_win);
552 }
553 mapMenu(menu, x, y);
554 }
555
556 static void
557 paintSubMenuArrow(Window win, int x, int y)
558 {
559 XPoint p[5];
560
561 p[0].x = p[4].x = x;
562 p[0].y = p[4].y = y - menuFont->ascent + 1;
563 p[1].x = p[0].x + MENU_ARROW_WIDTH - 1;
564 p[1].y = p[0].y + (menuFont->ascent / 2) - 1;
565 p[2].x = p[1].x;
566 p[2].y = p[1].y + 1;
567 p[3].x = p[0].x;
568 p[3].y = p[0].y + menuFont->ascent - 2;
569 XFillPolygon(__glutDisplay, win,
570 whiteGC, p, 4, Convex, CoordModeOrigin);
571 XDrawLines(__glutDisplay, win, blackGC, p, 5, CoordModeOrigin);
572 }
573
574 static void
575 paintMenuItem(GLUTmenuItem * item, int num)
576 {
577 Window win = item->menu->win;
578 GC gc;
579 int y;
580 int subMenuExtension;
581
582 if (item->menu->submenus > 0) {
583 subMenuExtension = MENU_ARROW_GAP + MENU_ARROW_WIDTH;
584 } else {
585 subMenuExtension = 0;
586 }
587 if (item->menu->highlighted == item) {
588 gc = whiteGC;
589 } else {
590 gc = grayGC;
591 }
592 y = MENU_GAP + fontHeight * num - menuFont->descent;
593 XFillRectangle(__glutDisplay, win, gc,
594 MENU_GAP, y - fontHeight + menuFont->descent,
595 item->menu->pixwidth + subMenuExtension, fontHeight);
596 XDrawString(__glutDisplay, win, blackGC,
597 MENU_GAP, y, item->label, item->len);
598 if (item->isTrigger) {
599 paintSubMenuArrow(win,
600 item->menu->pixwidth + MENU_ARROW_GAP + 1, y);
601 }
602 }
603
604 static void
605 paintMenu(GLUTmenu * menu)
606 {
607 GLUTmenuItem *item;
608 int i = menu->num;
609 int y = MENU_GAP + fontHeight * i - menuFont->descent;
610
611 item = menu->list;
612 while (item) {
613 if (item->menu->highlighted == item) {
614 paintMenuItem(item, i);
615 } else {
616 /* Quick render of the menu item; assume background
617 already cleared to gray. */
618 XDrawString(__glutDisplay, menu->win, blackGC,
619 2, y, item->label, item->len);
620 if (item->isTrigger) {
621 paintSubMenuArrow(menu->win,
622 menu->pixwidth + MENU_ARROW_GAP + 1, y);
623 }
624 }
625 i--;
626 y -= fontHeight;
627 item = item->next;
628 }
629 }
630
631 static GLUTmenuItem *
632 getMenuItem(GLUTmenu * menu, Window win, int *which)
633 {
634 GLUTmenuItem *item;
635 int i;
636
637 if (menu->searched) {
638 __glutFatalError("submenu infinite loop detected");
639 }
640 menu->searched = True;
641 i = menu->num;
642 item = menu->list;
643 while (item) {
644 if (item->win == win) {
645 *which = i;
646 menu->searched = False;
647 return item;
648 }
649 if (item->isTrigger) {
650 GLUTmenuItem *subitem;
651
652 subitem = __glutGetMenuItem(__glutMenuList[item->value],
653 win, which);
654 if (subitem) {
655 menu->searched = False;
656 return subitem;
657 }
658 }
659 i--;
660 item = item->next;
661 }
662 menu->searched = False;
663 return NULL;
664 }
665
666 static int
667 getMenuItemIndex(GLUTmenuItem * item)
668 {
669 int count = 0;
670
671 while (item) {
672 count++;
673 item = item->next;
674 }
675 return count;
676 }
677
678 static GLUTmenu *
679 getMenu(Window win)
680 {
681 GLUTmenu *menu;
682
683 menu = __glutMappedMenu;
684 while (menu) {
685 if (win == menu->win) {
686 return menu;
687 }
688 menu = menu->cascade;
689 }
690 return NULL;
691 }
692
693 static GLUTmenu *
694 getMenuByNum(int menunum)
695 {
696 if (menunum < 1 || menunum > menuListSize) {
697 return NULL;
698 }
699 return __glutMenuList[menunum - 1];
700 }
701
702 static int
703 getUnusedMenuSlot(void)
704 {
705 int i;
706
707 /* Look for allocated, unused slot. */
708 for (i = 0; i < menuListSize; i++) {
709 if (!__glutMenuList[i]) {
710 return i;
711 }
712 }
713 /* Allocate a new slot. */
714 menuListSize++;
715 if (__glutMenuList) {
716 __glutMenuList = (GLUTmenu **)
717 realloc(__glutMenuList, menuListSize * sizeof(GLUTmenu *));
718 } else {
719 /* XXX Some realloc's do not correctly perform a malloc
720 when asked to perform a realloc on a NULL pointer,
721 though the ANSI C library spec requires this. */
722 __glutMenuList = (GLUTmenu **) malloc(sizeof(GLUTmenu *));
723 }
724 if (!__glutMenuList) {
725 __glutFatalError("out of memory.");
726 }
727 __glutMenuList[menuListSize - 1] = NULL;
728 return menuListSize - 1;
729 }
730
731 void
732 __glutMenuModificationError(void)
733 {
734 /* XXX Remove the warning after GLUT 3.0. */
735 __glutWarning("The following is a new check for GLUT 3.0; update your code.");
736 __glutFatalError("menu manipulation not allowed while menus in use.");
737 }
738
739
740 static void
741 menuItemEnterOrLeave(GLUTmenuItem * item,
742 int num, int type)
743 {
744 int alreadyUp = 0;
745
746 if (type == EnterNotify) {
747 GLUTmenuItem *prevItem = item->menu->highlighted;
748
749 if (prevItem && prevItem != item) {
750 /* If there's an already higlighted item in this menu
751 that is different from this one (we could be
752 re-entering an item with an already cascaded
753 submenu!), unhighlight the previous item. */
754 item->menu->highlighted = NULL;
755 paintMenuItem(prevItem, getMenuItemIndex(prevItem));
756 }
757 item->menu->highlighted = item;
758 __glutItemSelected = item;
759 if (item->menu->cascade) {
760 if (!item->isTrigger) {
761 /* Entered a menu item that is not a submenu trigger,
762 so pop down the current submenu cascade of this
763 menu. */
764 unmapMenu(item->menu->cascade);
765 item->menu->cascade = NULL;
766 } else {
767 GLUTmenu *submenu = __glutMenuList[item->value];
768
769 if (submenu->anchor == item) {
770 /* We entered the submenu trigger for the submenu
771 that is already up, so don't take down the
772 submenu. */
773 alreadyUp = 1;
774 } else {
775 /* Submenu already popped up for some other submenu
776 item of this menu; need to pop down that other
777 submenu cascade. */
778 unmapMenu(item->menu->cascade);
779 item->menu->cascade = NULL;
780 }
781 }
782 }
783 if (!alreadyUp) {
784 /* Make sure the menu item gets painted with
785 highlighting. */
786 paintMenuItem(item, num);
787 } else {
788 /* If already up, should already be highlighted. */
789 }
790 } else {
791 /* LeaveNotify: Handle leaving a menu item... */
792 if (item->menu->cascade &&
793 item->menu->cascade->anchor == item) {
794 /* If there is a submenu casacaded from this item, do not
795 change the highlighting on this item upon leaving. */
796 } else {
797 /* Unhighlight this menu item. */
798 item->menu->highlighted = NULL;
799 paintMenuItem(item, num);
800 }
801 __glutItemSelected = NULL;
802 }
803 if (item->isTrigger) {
804 if (type == EnterNotify && !alreadyUp) {
805 GLUTmenu *submenu = __glutMenuList[item->value];
806
807 mapMenu(submenu,
808 item->menu->x + item->menu->pixwidth +
809 MENU_ARROW_GAP + MENU_ARROW_WIDTH +
810 MENU_GAP + MENU_BORDER,
811 item->menu->y + fontHeight * (num - 1) + MENU_GAP);
812 item->menu->cascade = submenu;
813 submenu->anchor = item;
814 }
815 }
816 }
817
818 /* Installs callback functions for use by glut_event.c The point
819 of this is so that GLUT's menu code only gets linked into
820 GLUT binaries (assuming a static library) if the GLUT menu
821 API is used. */
822 static void
823 installMenuCallbacks(void)
824 {
825 __glutMenuItemEnterOrLeave = menuItemEnterOrLeave;
826 __glutFinishMenu = finishMenu;
827 __glutPaintMenu = paintMenu;
828 __glutStartMenu = startMenu;
829 __glutGetMenuByNum = getMenuByNum;
830 __glutGetMenu = getMenu;
831 __glutGetMenuItem = getMenuItem;
832 }
833
834 int APIENTRY
835 glutCreateMenu(GLUTselectCB selectFunc)
836 {
837 XSetWindowAttributes wa;
838 GLUTmenu *menu;
839 int menuid;
840
841 if (__glutMappedMenu) {
842 __glutMenuModificationError();
843 }
844 if (!__glutDisplay) {
845 __glutOpenXConnection(NULL);
846 }
847
848 installMenuCallbacks();
849
850 menuid = getUnusedMenuSlot();
851 menu = (GLUTmenu *) malloc(sizeof(GLUTmenu));
852 if (!menu) {
853 __glutFatalError("out of memory.");
854 }
855 menu->id = menuid;
856 menu->num = 0;
857 menu->submenus = 0;
858 menu->managed = False;
859 menu->searched = False;
860 menu->pixwidth = 0;
861 menu->select = selectFunc;
862 menu->list = NULL;
863 menu->cascade = NULL;
864 menu->highlighted = NULL;
865 menu->anchor = NULL;
866 menuSetup();
867 wa.override_redirect = True;
868 wa.background_pixel = menuGray;
869 wa.border_pixel = menuBlack;
870 wa.colormap = menuColormap;
871 wa.event_mask = StructureNotifyMask | ExposureMask |
872 ButtonPressMask | ButtonReleaseMask |
873 EnterWindowMask | LeaveWindowMask;
874 /* Save unders really only enabled if useSaveUnders is set to
875 CWSaveUnder, ie. using Mesa 3D. See earlier comments. */
876 wa.save_under = True;
877 menu->win = XCreateWindow(__glutDisplay, __glutRoot,
878 /* Real position determined when mapped. */
879 0, 0,
880 /* Real size will be determined when menu is manged. */
881 1, 1,
882 MENU_BORDER, menuDepth, InputOutput, menuVisual,
883 CWOverrideRedirect | CWBackPixel | CWBorderPixel |
884 CWEventMask | CWColormap | useSaveUnders,
885 &wa);
886 menuGraphicsContextSetup(menu->win);
887 __glutMenuList[menuid] = menu;
888 __glutSetMenu(menu);
889 return menuid + 1;
890 }
891
892 /* CENTRY */
893 int APIENTRY
894 glutGetMenu(void)
895 {
896 if (__glutCurrentMenu) {
897 return __glutCurrentMenu->id + 1;
898 } else {
899 return 0;
900 }
901 }
902
903 void APIENTRY
904 glutSetMenu(int menuid)
905 {
906 GLUTmenu *menu;
907
908 if (menuid < 1 || menuid > menuListSize) {
909 __glutWarning("glutSetMenu attempted on bogus menu.");
910 return;
911 }
912 menu = __glutMenuList[menuid - 1];
913 if (!menu) {
914 __glutWarning("glutSetMenu attempted on bogus menu.");
915 return;
916 }
917 __glutSetMenu(menu);
918 }
919 /* ENDCENTRY */
920
921 void
922 __glutSetMenuItem(GLUTmenuItem * item, const char *label,
923 int value, Bool isTrigger)
924 {
925 GLUTmenu *menu;
926
927 menu = item->menu;
928 item->label = __glutStrdup(label);
929 if (!item->label) {
930 __glutFatalError("out of memory.");
931 }
932 item->isTrigger = isTrigger;
933 item->len = (int) strlen(label);
934 item->value = value;
935 item->pixwidth = XTextWidth(menuFont, label, item->len) + 4;
936 if (item->pixwidth > menu->pixwidth) {
937 menu->pixwidth = item->pixwidth;
938 }
939 menu->managed = False;
940 }
941
942 /* CENTRY */
943 void APIENTRY
944 glutAddMenuEntry(const char *label, int value)
945 {
946 XSetWindowAttributes wa;
947 GLUTmenuItem *entry;
948
949 if (__glutMappedMenu) {
950 __glutMenuModificationError();
951 }
952 entry = (GLUTmenuItem *) malloc(sizeof(GLUTmenuItem));
953 if (!entry) {
954 __glutFatalError("out of memory.");
955 }
956 entry->menu = __glutCurrentMenu;
957 __glutSetMenuItem(entry, label, value, False);
958 wa.event_mask = EnterWindowMask | LeaveWindowMask;
959 entry->win = XCreateWindow(__glutDisplay,
960 __glutCurrentMenu->win, MENU_GAP,
961 __glutCurrentMenu->num * fontHeight + MENU_GAP, /* x & y */
962 entry->pixwidth, fontHeight, /* width & height */
963 0, CopyFromParent, InputOnly, CopyFromParent,
964 CWEventMask, &wa);
965 XMapWindow(__glutDisplay, entry->win);
966 __glutCurrentMenu->num++;
967 entry->next = __glutCurrentMenu->list;
968 __glutCurrentMenu->list = entry;
969 }
970
971 void APIENTRY
972 glutAddSubMenu(const char *label, int menu)
973 {
974 XSetWindowAttributes wa;
975 GLUTmenuItem *submenu;
976
977 if (__glutMappedMenu) {
978 __glutMenuModificationError();
979 }
980 submenu = (GLUTmenuItem *) malloc(sizeof(GLUTmenuItem));
981 if (!submenu) {
982 __glutFatalError("out of memory.");
983 }
984 __glutCurrentMenu->submenus++;
985 submenu->menu = __glutCurrentMenu;
986 __glutSetMenuItem(submenu, label, /* base 0 */ menu - 1, True);
987 wa.event_mask = EnterWindowMask | LeaveWindowMask;
988 submenu->win = XCreateWindow(__glutDisplay,
989 __glutCurrentMenu->win, MENU_GAP,
990 __glutCurrentMenu->num * fontHeight + MENU_GAP, /* x & y */
991 submenu->pixwidth, fontHeight, /* width & height */
992 0, CopyFromParent, InputOnly, CopyFromParent,
993 CWEventMask, &wa);
994 XMapWindow(__glutDisplay, submenu->win);
995 __glutCurrentMenu->num++;
996 submenu->next = __glutCurrentMenu->list;
997 __glutCurrentMenu->list = submenu;
998 }
999
1000 void APIENTRY
1001 glutAttachMenu(int button)
1002 {
1003 if (__glutMappedMenu) {
1004 __glutMenuModificationError();
1005 }
1006 installMenuCallbacks();
1007 if (__glutCurrentWindow->menu[button] < 1) {
1008 __glutCurrentWindow->buttonUses++;
1009 }
1010 __glutChangeWindowEventMask(
1011 ButtonPressMask | ButtonReleaseMask, True);
1012 __glutCurrentWindow->menu[button] = __glutCurrentMenu->id + 1;
1013 }
1014 /* ENDCENTRY */