1
2/* Copyright (c) Mark J. Kilgard, 1994, 1997, 1998. */
3/* Copyright (c) Nate Robins, 1997. */
4
5/* This program is freely distributable without licensing fees
6   and is provided without guarantee or warrantee expressed or
7   implied. This program is -not- in the public domain. */
8
9/* This file completely re-implements glut_menu.c and glut_menu2.c
10   for Win32.  Note that neither glut_menu.c nor glut_menu2.c are
11   compiled into Win32 GLUT. */
12
13#include <stdlib.h>
14#include <string.h>
15#include <stdio.h>
16#include <errno.h>
17#include <assert.h>
18
19#include "glutint.h"
20
21void (GLUTCALLBACK *__glutMenuStatusFunc) (int, int, int);
22GLUTmenu *__glutMappedMenu;
23GLUTwindow *__glutMenuWindow;
24GLUTmenuItem *__glutItemSelected;
25unsigned __glutMenuButton;
26
27static GLUTmenu **menuList = NULL;
28static int menuListSize = 0;
29static UINT uniqueMenuHandler = 1;
30
31/* DEPRICATED, use glutMenuStatusFunc instead. */
32void GLUTAPIENTRY
33glutMenuStateFunc(GLUTmenuStateCB menuStateFunc)
34{
35  __glutMenuStatusFunc = (GLUTmenuStatusCB) menuStateFunc;
36}
37
38void GLUTAPIENTRY
39glutMenuStatusFunc(GLUTmenuStatusCB menuStatusFunc)
40{
41  __glutMenuStatusFunc = menuStatusFunc;
42}
43
44void
45__glutSetMenu(GLUTmenu * menu)
46{
47  __glutCurrentMenu = menu;
48}
49
50static void
51unmapMenu(GLUTmenu * menu)
52{
53  if (menu->cascade) {
54    unmapMenu(menu->cascade);
55    menu->cascade = NULL;
56  }
57  menu->anchor = NULL;
58  menu->highlighted = NULL;
59}
60
61void
62__glutFinishMenu(Window win, int x, int y)
63{
64
65  unmapMenu(__glutMappedMenu);
66
67  /* XXX Put in a GdiFlush just in case.  Probably unnecessary. -mjk  */
68  GdiFlush();
69
70  if (__glutMenuStatusFunc) {
71    __glutSetWindow(__glutMenuWindow);
72    __glutSetMenu(__glutMappedMenu);
73
74    /* Setting __glutMappedMenu to NULL permits operations that
75       change menus or destroy the menu window again. */
76    __glutMappedMenu = NULL;
77
78    __glutMenuStatusFunc(GLUT_MENU_NOT_IN_USE, x, y);
79  }
80  /* Setting __glutMappedMenu to NULL permits operations that
81     change menus or destroy the menu window again. */
82  __glutMappedMenu = NULL;
83
84  /* If an item is selected and it is not a submenu trigger,
85     generate menu callback. */
86  if (__glutItemSelected && !__glutItemSelected->isTrigger) {
87    __glutSetWindow(__glutMenuWindow);
88    /* When menu callback is triggered, current menu should be
89       set to the callback menu. */
90    __glutSetMenu(__glutItemSelected->menu);
91    __glutItemSelected->menu->select(__glutItemSelected->value);
92  }
93  __glutMenuWindow = NULL;
94}
95
96static void
97mapMenu(GLUTmenu * menu, int x, int y)
98{
99  TrackPopupMenu((HMENU) menu->win, TPM_LEFTALIGN |
100    ((__glutMenuButton == TPM_RIGHTBUTTON) ? TPM_RIGHTBUTTON : TPM_LEFTBUTTON),
101    x, y, 0, __glutCurrentWindow->win, NULL);
102}
103
104void
105__glutStartMenu(GLUTmenu * menu, GLUTwindow * window,
106		int x, int y, int x_win, int y_win)
107{
108  assert(__glutMappedMenu == NULL);
109  __glutMappedMenu = menu;
110  __glutMenuWindow = window;
111  __glutItemSelected = NULL;
112  if (__glutMenuStatusFunc) {
113    __glutSetMenu(menu);
114    __glutSetWindow(window);
115    __glutMenuStatusFunc(GLUT_MENU_IN_USE, x_win, y_win);
116  }
117  mapMenu(menu, x, y);
118}
119
120GLUTmenuItem *
121__glutGetUniqueMenuItem(GLUTmenu * menu, UINT unique)
122{
123  GLUTmenuItem *item;
124  int i;
125
126  i = menu->num;
127  item = menu->list;
128  while (item) {
129    if (item->unique == unique) {
130      return item;
131    }
132    if (item->isTrigger) {
133      GLUTmenuItem *subitem;
134      subitem = __glutGetUniqueMenuItem(menuList[item->value], unique);
135      if (subitem) {
136        return subitem;
137      }
138    }
139    i--;
140    item = item->next;
141  }
142  return NULL;
143}
144
145GLUTmenuItem *
146__glutGetMenuItem(GLUTmenu * menu, Window win, int *which)
147{
148  GLUTmenuItem *item;
149  int i;
150
151  i = menu->num;
152  item = menu->list;
153  while (item) {
154    if (item->win == win) {
155      *which = i;
156      return item;
157    }
158    if (item->isTrigger) {
159      GLUTmenuItem *subitem;
160
161      subitem = __glutGetMenuItem(menuList[item->value],
162        win, which);
163      if (subitem) {
164        return subitem;
165      }
166    }
167    i--;
168    item = item->next;
169  }
170  return NULL;
171}
172
173GLUTmenu *
174__glutGetMenu(Window win)
175{
176  GLUTmenu *menu;
177
178  menu = __glutMappedMenu;
179  while (menu) {
180    if (win == menu->win) {
181      return menu;
182    }
183    menu = menu->cascade;
184  }
185  return NULL;
186}
187
188GLUTmenu *
189__glutGetMenuByNum(int menunum)
190{
191  if (menunum < 1 || menunum > menuListSize) {
192    return NULL;
193  }
194  return menuList[menunum - 1];
195}
196
197static int
198getUnusedMenuSlot(void)
199{
200  int i;
201
202  /* Look for allocated, unused slot. */
203  for (i = 0; i < menuListSize; i++) {
204    if (!menuList[i]) {
205      return i;
206    }
207  }
208  /* Allocate a new slot. */
209  menuListSize++;
210  if (menuList) {
211    menuList = (GLUTmenu **)
212      realloc(menuList, menuListSize * sizeof(GLUTmenu *));
213  } else {
214    /* XXX Some realloc's do not correctly perform a malloc
215       when asked to perform a realloc on a NULL pointer,
216       though the ANSI C library spec requires this. */
217    menuList = (GLUTmenu **) malloc(sizeof(GLUTmenu *));
218  }
219  if (!menuList) {
220    __glutFatalError("out of memory.");
221  }
222  menuList[menuListSize - 1] = NULL;
223  return menuListSize - 1;
224}
225
226static void
227menuModificationError(void)
228{
229  /* XXX Remove the warning after GLUT 3.0. */
230  __glutWarning("The following is a new check for GLUT 3.0; update your code.");
231  __glutFatalError("menu manipulation not allowed while menus in use.");
232}
233
234int GLUTAPIENTRY
235glutCreateMenu(GLUTselectCB selectFunc)
236{
237  GLUTmenu *menu;
238  int menuid;
239
240  if (__glutMappedMenu) {
241    menuModificationError();
242  }
243  menuid = getUnusedMenuSlot();
244  menu = (GLUTmenu *) malloc(sizeof(GLUTmenu));
245  if (!menu) {
246    __glutFatalError("out of memory.");
247  }
248  menu->id = menuid;
249  menu->num = 0;
250  menu->submenus = 0;
251  menu->select = selectFunc;
252  menu->list = NULL;
253  menu->cascade = NULL;
254  menu->highlighted = NULL;
255  menu->anchor = NULL;
256  menu->win = (HWND) CreatePopupMenu();
257  menuList[menuid] = menu;
258  __glutSetMenu(menu);
259  return menuid + 1;
260}
261
262int GLUTAPIENTRY
263__glutCreateMenuWithExit(GLUTselectCB selectFunc, void (__cdecl *exitfunc)(int))
264{
265  __glutExitFunc = exitfunc;
266  return glutCreateMenu(selectFunc);
267}
268
269void GLUTAPIENTRY
270glutDestroyMenu(int menunum)
271{
272  GLUTmenu *menu = __glutGetMenuByNum(menunum);
273  GLUTmenuItem *item, *next;
274
275  if (__glutMappedMenu) {
276    menuModificationError();
277  }
278  assert(menu->id == menunum - 1);
279  DestroyMenu( (HMENU) menu->win);
280  menuList[menunum - 1] = NULL;
281  /* free all menu entries */
282  item = menu->list;
283  while (item) {
284    assert(item->menu == menu);
285    next = item->next;
286    free(item->label);
287    free(item);
288    item = next;
289  }
290  if (__glutCurrentMenu == menu) {
291    __glutCurrentMenu = NULL;
292  }
293  free(menu);
294}
295
296int GLUTAPIENTRY
297glutGetMenu(void)
298{
299  if (__glutCurrentMenu) {
300    return __glutCurrentMenu->id + 1;
301  } else {
302    return 0;
303  }
304}
305
306void GLUTAPIENTRY
307glutSetMenu(int menuid)
308{
309  GLUTmenu *menu;
310
311  if (menuid < 1 || menuid > menuListSize) {
312    __glutWarning("glutSetMenu attempted on bogus menu.");
313    return;
314  }
315  menu = menuList[menuid - 1];
316  if (!menu) {
317    __glutWarning("glutSetMenu attempted on bogus menu.");
318    return;
319  }
320  __glutSetMenu(menu);
321}
322
323static void
324setMenuItem(GLUTmenuItem * item, const char *label,
325	    int value, Bool isTrigger)
326{
327  GLUTmenu *menu;
328
329  menu = item->menu;
330  item->label = __glutStrdup(label);
331  if (!item->label) {
332    __glutFatalError("out of memory.");
333  }
334  item->isTrigger = isTrigger;
335  item->len = (int) strlen(label);
336  item->value = value;
337  item->unique = uniqueMenuHandler++;
338  if (isTrigger) {
339    AppendMenu((HMENU) menu->win, MF_POPUP, (UINT)item->win, label);
340  } else {
341    AppendMenu((HMENU) menu->win, MF_STRING, item->unique, label);
342  }
343}
344
345void GLUTAPIENTRY
346glutAddMenuEntry(const char *label, int value)
347{
348  GLUTmenuItem *entry;
349
350  if (__glutMappedMenu) {
351    menuModificationError();
352  }
353  entry = (GLUTmenuItem *) malloc(sizeof(GLUTmenuItem));
354  if (!entry) {
355    __glutFatalError("out of memory.");
356  }
357  entry->menu = __glutCurrentMenu;
358  setMenuItem(entry, label, value, FALSE);
359  __glutCurrentMenu->num++;
360  entry->next = __glutCurrentMenu->list;
361  __glutCurrentMenu->list = entry;
362}
363
364void GLUTAPIENTRY
365glutAddSubMenu(const char *label, int menu)
366{
367  GLUTmenuItem *submenu;
368  GLUTmenu     *popupmenu;
369
370  if (__glutMappedMenu) {
371    menuModificationError();
372  }
373  submenu = (GLUTmenuItem *) malloc(sizeof(GLUTmenuItem));
374  if (!submenu) {
375    __glutFatalError("out of memory.");
376  }
377  __glutCurrentMenu->submenus++;
378  submenu->menu = __glutCurrentMenu;
379  popupmenu = __glutGetMenuByNum(menu);
380  if (popupmenu) {
381    submenu->win = popupmenu->win;
382  }
383  setMenuItem(submenu, label, /* base 0 */ menu - 1, TRUE);
384  __glutCurrentMenu->num++;
385  submenu->next = __glutCurrentMenu->list;
386  __glutCurrentMenu->list = submenu;
387}
388
389void GLUTAPIENTRY
390glutChangeToMenuEntry(int num, const char *label, int value)
391{
392  GLUTmenuItem *item;
393  int i;
394
395  if (__glutMappedMenu) {
396    menuModificationError();
397  }
398  i = __glutCurrentMenu->num;
399  item = __glutCurrentMenu->list;
400  while (item) {
401    if (i == num) {
402      if (item->isTrigger) {
403        /* If changing a submenu trigger to a menu entry, we
404           need to account for submenus.  */
405        item->menu->submenus--;
406	/* Nuke the Win32 menu. */
407	DestroyMenu((HMENU) item->win);
408      }
409      free(item->label);
410
411      item->label = strdup(label);
412      if (!item->label)
413	__glutFatalError("out of memory");
414      item->isTrigger = FALSE;
415      item->len = (int) strlen(label);
416      item->value = value;
417      item->unique = uniqueMenuHandler++;
418      ModifyMenu((HMENU) __glutCurrentMenu->win, (UINT) i - 1,
419        MF_BYPOSITION | MFT_STRING, item->unique, label);
420
421      return;
422    }
423    i--;
424    item = item->next;
425  }
426  __glutWarning("Current menu has no %d item.", num);
427}
428
429void GLUTAPIENTRY
430glutChangeToSubMenu(int num, const char *label, int menu)
431{
432  GLUTmenu *popupmenu;
433  GLUTmenuItem *item;
434  int i;
435
436  if (__glutMappedMenu) {
437    menuModificationError();
438  }
439  i = __glutCurrentMenu->num;
440  item = __glutCurrentMenu->list;
441  while (item) {
442    if (i == num) {
443      if (!item->isTrigger) {
444        /* If changing a menu entry to as submenu trigger, we
445           need to account for submenus.  */
446        item->menu->submenus++;
447	item->win = (HWND) CreatePopupMenu();
448      }
449      free(item->label);
450
451      item->label = strdup(label);
452      if (!item->label)
453	__glutFatalError("out of memory");
454      item->isTrigger = TRUE;
455      item->len = (int) strlen(label);
456      item->value = menu - 1;
457      item->unique = uniqueMenuHandler++;
458      popupmenu = __glutGetMenuByNum(menu);
459      if (popupmenu)
460	item->win = popupmenu->win;
461      ModifyMenu((HMENU) __glutCurrentMenu->win, (UINT) i - 1,
462        MF_BYPOSITION | MF_POPUP, (UINT) item->win, label);
463      return;
464    }
465    i--;
466    item = item->next;
467  }
468  __glutWarning("Current menu has no %d item.", num);
469}
470
471void GLUTAPIENTRY
472glutRemoveMenuItem(int num)
473{
474  GLUTmenuItem *item, **prev;
475  int i;
476
477  if (__glutMappedMenu) {
478    menuModificationError();
479  }
480  i = __glutCurrentMenu->num;
481  prev = &__glutCurrentMenu->list;
482  item = __glutCurrentMenu->list;
483  while (item) {
484    if (i == num) {
485      /* Found the menu item in list to remove. */
486      __glutCurrentMenu->num--;
487
488      /* Patch up menu's item list. */
489      *prev = item->next;
490
491      RemoveMenu((HMENU) __glutCurrentMenu->win, (UINT) i - 1, MF_BYPOSITION);
492
493      free(item->label);
494      free(item);
495      return;
496    }
497    i--;
498    prev = &item->next;
499    item = item->next;
500  }
501  __glutWarning("Current menu has no %d item.", num);
502}
503
504void GLUTAPIENTRY
505glutAttachMenu(int button)
506{
507  if (__glutCurrentWindow == __glutGameModeWindow) {
508    __glutWarning("cannot attach menus in game mode.");
509    return;
510  }
511  if (__glutMappedMenu) {
512    menuModificationError();
513  }
514  if (__glutCurrentWindow->menu[button] < 1) {
515    __glutCurrentWindow->buttonUses++;
516  }
517  __glutCurrentWindow->menu[button] = __glutCurrentMenu->id + 1;
518}
519
520void GLUTAPIENTRY
521glutDetachMenu(int button)
522{
523  if (__glutMappedMenu) {
524    menuModificationError();
525  }
526  if (__glutCurrentWindow->menu[button] > 0) {
527    __glutCurrentWindow->buttonUses--;
528    __glutCurrentWindow->menu[button] = 0;
529  }
530}
531
532