4ea023a8145599e5f54d3a0df7705226263d9730
[freeglut] / src / Common / freeglut_menu.c
1 /*\r
2  * freeglut_menu.c\r
3  *\r
4  * Pull-down menu creation and handling.\r
5  *\r
6  * Copyright (c) 1999-2000 Pawel W. Olszta. All Rights Reserved.\r
7  * Written by Pawel W. Olszta, <olszta@sourceforge.net>\r
8  * Creation date: Thu Dec 16 1999\r
9  *\r
10  * Permission is hereby granted, free of charge, to any person obtaining a\r
11  * copy of this software and associated documentation files (the "Software"),\r
12  * to deal in the Software without restriction, including without limitation\r
13  * the rights to use, copy, modify, merge, publish, distribute, sublicense,\r
14  * and/or sell copies of the Software, and to permit persons to whom the\r
15  * Software is furnished to do so, subject to the following conditions:\r
16  *\r
17  * The above copyright notice and this permission notice shall be included\r
18  * in all copies or substantial portions of the Software.\r
19  *\r
20  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS\r
21  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r
22  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL\r
23  * PAWEL W. OLSZTA BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\r
24  * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\r
25  * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\r
26  */\r
27 \r
28 #define FREEGLUT_BUILDING_LIB\r
29 #include <GL/freeglut.h>\r
30 #include "freeglut_internal.h"\r
31 \r
32 /* -- DEFINITIONS ---------------------------------------------------------- */\r
33 \r
34 /*\r
35  * FREEGLUT_MENU_FONT can be any freeglut bitmapped font.\r
36  * (Stroked fonts would not be out of the question, but we'd need to alter\r
37  *  code, since GLUT (hence freeglut) does not quite unify stroked and\r
38  *  bitmapped font handling.)\r
39  * Old UNIX/X11 GLUT (BSD, UNIX, IRIX, LINUX, HPUX, ...) used a system\r
40  * font best approximated by an 18-pixel HELVETICA, I think.  MS-WINDOWS\r
41  * GLUT used something closest to the 8x13 fixed-width font.  (Old\r
42  * GLUT apparently uses host-system menus rather than building its own.\r
43  * freeglut is building its own menus from scratch.)\r
44  *\r
45  * FREEGLUT_MENU_HEIGHT gives the height of ONE menu box.  This should be\r
46  * the distances between two adjacent menu entries.  It should scale\r
47  * automatically with the font choice, so you needn't alter it---unless you\r
48  * use a stroked font.\r
49  *\r
50  * FREEGLUT_MENU_BORDER says how many pixels to allow around the edge of a\r
51  * menu.  (It also seems to be the same as the number of pixels used as\r
52  * a border around *items* to separate them from neighbors.  John says\r
53  * that that wasn't the original intent...if not, perhaps we need another\r
54  * symbolic constant, FREEGLUT_MENU_ITEM_BORDER, or such.)\r
55  */\r
56 /* See platform-specific header files for menu font and color definitions */\r
57 \r
58 #define  FREEGLUT_MENU_HEIGHT  (glutBitmapHeight(FREEGLUT_MENU_FONT) + \\r
59                                 FREEGLUT_MENU_BORDER)\r
60 #define  FREEGLUT_MENU_BORDER   2\r
61 \r
62 \r
63 /*\r
64  * These variables are for rendering the freeglut menu items.\r
65  *\r
66  * The choices are fore- and background, with and without h for Highlighting.\r
67  * Old GLUT appeared to be system-dependant for its colors (sigh) so we are\r
68  * too.  These variables should be stuffed into global state and initialized\r
69  * via the glutInit*() system.\r
70  */\r
71 static float menu_pen_fore  [4] = FREEGLUT_MENU_PEN_FORE_COLORS ;\r
72 static float menu_pen_back  [4] = FREEGLUT_MENU_PEN_BACK_COLORS ;\r
73 static float menu_pen_hfore [4] = FREEGLUT_MENU_PEN_HFORE_COLORS;\r
74 static float menu_pen_hback [4] = FREEGLUT_MENU_PEN_HBACK_COLORS;\r
75 \r
76 \r
77 extern GLvoid fgPlatformGetGameModeVMaxExtent( SFG_Window* window, int* x, int* y );\r
78 \r
79 /* -- PRIVATE FUNCTIONS ---------------------------------------------------- */\r
80 \r
81 /*\r
82  * Private function to find a menu entry by index\r
83  */\r
84 static SFG_MenuEntry *fghFindMenuEntry( SFG_Menu* menu, int index )\r
85 {\r
86     SFG_MenuEntry *entry;\r
87     int i = 1;\r
88 \r
89     for( entry = (SFG_MenuEntry *)menu->Entries.First;\r
90          entry;\r
91          entry = (SFG_MenuEntry *)entry->Node.Next )\r
92     {\r
93         if( i == index )\r
94             break;\r
95         ++i;\r
96     }\r
97 \r
98     return entry;\r
99 }\r
100 \r
101 /*\r
102  * Deactivates a menu pointed by the function argument.\r
103  */\r
104 static void fghDeactivateSubMenu( SFG_MenuEntry *menuEntry )\r
105 {\r
106     SFG_MenuEntry *subMenuIter;\r
107     /* Hide the present menu's window */\r
108     fgSetWindow( menuEntry->SubMenu->Window );\r
109     glutHideWindow( );\r
110 \r
111     /* Forget about having that menu active anymore, now: */\r
112     menuEntry->SubMenu->Window->ActiveMenu = NULL;\r
113     menuEntry->SubMenu->IsActive = GL_FALSE;\r
114     menuEntry->SubMenu->ActiveEntry = NULL;\r
115 \r
116     /* Hide all submenu windows, and the root menu's window. */\r
117     for ( subMenuIter = (SFG_MenuEntry *)menuEntry->SubMenu->Entries.First;\r
118           subMenuIter;\r
119           subMenuIter = (SFG_MenuEntry *)subMenuIter->Node.Next )\r
120     {\r
121         subMenuIter->IsActive = GL_FALSE;\r
122 \r
123         /* Is that an active submenu by any case? */\r
124         if( subMenuIter->SubMenu )\r
125             fghDeactivateSubMenu( subMenuIter );\r
126     }\r
127 \r
128     fgSetWindow ( menuEntry->SubMenu->ParentWindow ) ;\r
129 }\r
130 \r
131 /*\r
132  * Private function to get the virtual maximum screen extent\r
133  */\r
134 static GLvoid fghGetVMaxExtent( SFG_Window* window, int* x, int* y )\r
135 {\r
136     if( fgStructure.GameModeWindow )\r
137                 fgPlatformGetGameModeVMaxExtent ( window, x, y );\r
138     else\r
139     {\r
140         *x = fgDisplay.ScreenWidth;\r
141         *y = fgDisplay.ScreenHeight;\r
142     }\r
143 }\r
144 \r
145 /*\r
146  * Private function to check for the current menu/sub menu activity state\r
147  */\r
148 static GLboolean fghCheckMenuStatus( SFG_Menu* menu )\r
149 {\r
150     SFG_MenuEntry* menuEntry;\r
151     int x, y;\r
152 \r
153     /* First of all check any of the active sub menus... */\r
154     for( menuEntry = (SFG_MenuEntry *)menu->Entries.First;\r
155          menuEntry;\r
156          menuEntry = (SFG_MenuEntry *)menuEntry->Node.Next )\r
157     {\r
158         if( menuEntry->SubMenu && menuEntry->IsActive )\r
159         {\r
160             /*\r
161              * OK, have the sub-menu checked, too. If it returns GL_TRUE, it\r
162              * will mean that it caught the mouse cursor and we do not need\r
163              * to regenerate the activity list, and so our parents do...\r
164              */\r
165             GLboolean return_status;\r
166 \r
167             menuEntry->SubMenu->Window->State.MouseX =\r
168                 menu->Window->State.MouseX + menu->X - menuEntry->SubMenu->X;\r
169             menuEntry->SubMenu->Window->State.MouseY =\r
170                 menu->Window->State.MouseY + menu->Y - menuEntry->SubMenu->Y;\r
171             return_status = fghCheckMenuStatus( menuEntry->SubMenu );\r
172 \r
173             if ( return_status )\r
174                 return GL_TRUE;\r
175         }\r
176     }\r
177 \r
178     /* That much about our sub menus, let's get to checking the current menu: */\r
179     x = menu->Window->State.MouseX;\r
180     y = menu->Window->State.MouseY;\r
181 \r
182     /* Check if the mouse cursor is contained within the current menu box */\r
183     if( ( x >= FREEGLUT_MENU_BORDER ) &&\r
184         ( x < menu->Width  - FREEGLUT_MENU_BORDER ) &&\r
185         ( y >= FREEGLUT_MENU_BORDER ) &&\r
186         ( y < menu->Height - FREEGLUT_MENU_BORDER )  )\r
187     {\r
188         int menuID = ( y - FREEGLUT_MENU_BORDER ) / FREEGLUT_MENU_HEIGHT;\r
189 \r
190         /* The mouse cursor is somewhere over our box, check it out. */\r
191         menuEntry = fghFindMenuEntry( menu, menuID + 1 );\r
192         FREEGLUT_INTERNAL_ERROR_EXIT( menuEntry, "Cannot find menu entry",\r
193                                       "fghCheckMenuStatus" );\r
194 \r
195         menuEntry->IsActive = GL_TRUE;\r
196         menuEntry->Ordinal = menuID;\r
197 \r
198         /*\r
199          * If this is not the same as the last active menu entry, deactivate\r
200          * the previous entry.  Specifically, if the previous active entry\r
201          * was a submenu then deactivate it.\r
202          */\r
203         if( menu->ActiveEntry && ( menuEntry != menu->ActiveEntry ) )\r
204             if( menu->ActiveEntry->SubMenu )\r
205                 fghDeactivateSubMenu( menu->ActiveEntry );\r
206 \r
207         if( menuEntry != menu->ActiveEntry )\r
208         {\r
209             menu->Window->State.Redisplay = GL_TRUE;\r
210             if( menu->ActiveEntry )\r
211                 menu->ActiveEntry->IsActive = GL_FALSE;\r
212         }\r
213 \r
214         menu->ActiveEntry = menuEntry;\r
215         menu->IsActive = GL_TRUE;  /* XXX Do we need this? */\r
216 \r
217         /*\r
218          * OKi, we have marked that entry as active, but it would be also\r
219          * nice to have its contents updated, in case it's a sub menu.\r
220          * Also, ignore the return value of the check function:\r
221          */\r
222         if( menuEntry->SubMenu )\r
223         {\r
224             if ( ! menuEntry->SubMenu->IsActive )\r
225             {\r
226                 int max_x, max_y;\r
227                 SFG_Window *current_window = fgStructure.CurrentWindow;\r
228 \r
229                 /* Set up the initial menu position now... */\r
230                 menuEntry->SubMenu->IsActive = GL_TRUE;\r
231 \r
232                 /* Set up the initial submenu position now: */\r
233                 fghGetVMaxExtent(menu->ParentWindow, &max_x, &max_y);\r
234                 menuEntry->SubMenu->X = menu->X + menu->Width;\r
235                 menuEntry->SubMenu->Y = menu->Y +\r
236                     menuEntry->Ordinal * FREEGLUT_MENU_HEIGHT;\r
237 \r
238                 if( menuEntry->SubMenu->X + menuEntry->SubMenu->Width > max_x )\r
239                     menuEntry->SubMenu->X = menu->X - menuEntry->SubMenu->Width;\r
240 \r
241                 if( menuEntry->SubMenu->Y + menuEntry->SubMenu->Height > max_y )\r
242                 {\r
243                     menuEntry->SubMenu->Y -= ( menuEntry->SubMenu->Height -\r
244                                                FREEGLUT_MENU_HEIGHT -\r
245                                                2 * FREEGLUT_MENU_BORDER );\r
246                     if( menuEntry->SubMenu->Y < 0 )\r
247                         menuEntry->SubMenu->Y = 0;\r
248                 }\r
249 \r
250                 fgSetWindow( menuEntry->SubMenu->Window );\r
251                 glutPositionWindow( menuEntry->SubMenu->X,\r
252                                     menuEntry->SubMenu->Y );\r
253                 glutReshapeWindow( menuEntry->SubMenu->Width,\r
254                                    menuEntry->SubMenu->Height );\r
255                 glutPopWindow( );\r
256                 glutShowWindow( );\r
257                 menuEntry->SubMenu->Window->ActiveMenu = menuEntry->SubMenu;\r
258                 fgSetWindow( current_window );\r
259                 menuEntry->SubMenu->Window->State.MouseX =\r
260                     x + menu->X - menuEntry->SubMenu->X;\r
261                 menuEntry->SubMenu->Window->State.MouseY =\r
262                     y + menu->Y - menuEntry->SubMenu->Y;\r
263                 fghCheckMenuStatus( menuEntry->SubMenu );\r
264             }\r
265 \r
266             /* Activate it because its parent entry is active */\r
267             menuEntry->SubMenu->IsActive = GL_TRUE;  /* XXX Do we need this? */\r
268         }\r
269 \r
270         /* Report back that we have caught the menu cursor */\r
271         return GL_TRUE;\r
272     }\r
273 \r
274     /* Looks like the menu cursor is somewhere else... */\r
275     if( menu->ActiveEntry && menu->ActiveEntry->IsActive &&\r
276         ( !menu->ActiveEntry->SubMenu ||\r
277           !menu->ActiveEntry->SubMenu->IsActive ) )\r
278     {\r
279         menu->Window->State.Redisplay = GL_TRUE;\r
280         menu->ActiveEntry->IsActive = GL_FALSE;\r
281         menu->ActiveEntry = NULL;\r
282     }\r
283 \r
284     return GL_FALSE;\r
285 }\r
286 \r
287 /*\r
288  * Displays a menu box and all of its submenus (if they are active)\r
289  */\r
290 static void fghDisplayMenuBox( SFG_Menu* menu )\r
291 {\r
292     SFG_MenuEntry *menuEntry;\r
293     int i;\r
294     int border = FREEGLUT_MENU_BORDER;\r
295 \r
296     /*\r
297      * Have the menu box drawn first. The +- values are\r
298      * here just to make it more nice-looking...\r
299      */\r
300     /* a non-black dark version of the below. */\r
301     glColor4f( 1.0f, 1.0f, 1.0f, 1.0f );\r
302     glBegin( GL_QUAD_STRIP );\r
303         glVertex2i( menu->Width         , 0                    );\r
304         glVertex2i( menu->Width - border,                border);\r
305         glVertex2i( 0                   , 0                    );\r
306         glVertex2i(               border,                border);\r
307         glVertex2i( 0                   , menu->Height         );\r
308         glVertex2i(               border, menu->Height - border);\r
309     glEnd( );\r
310 \r
311     /* a non-black dark version of the below. */\r
312     glColor4f( 0.5f, 0.5f, 0.5f, 1.0f );\r
313     glBegin( GL_QUAD_STRIP );\r
314         glVertex2i( 0                   , menu->Height         );\r
315         glVertex2i(               border, menu->Height - border);\r
316         glVertex2i( menu->Width         , menu->Height         );\r
317         glVertex2i( menu->Width - border, menu->Height - border);\r
318         glVertex2i( menu->Width         , 0                    );\r
319         glVertex2i( menu->Width - border,                border);\r
320     glEnd( );\r
321 \r
322     glColor4fv( menu_pen_back );\r
323     glBegin( GL_QUADS );\r
324         glVertex2i(               border,                border);\r
325         glVertex2i( menu->Width - border,                border);\r
326         glVertex2i( menu->Width - border, menu->Height - border);\r
327         glVertex2i(               border, menu->Height - border);\r
328     glEnd( );\r
329 \r
330     /* Check if any of the submenus is currently active... */\r
331     for( menuEntry = (SFG_MenuEntry *)menu->Entries.First;\r
332          menuEntry;\r
333          menuEntry = (SFG_MenuEntry *)menuEntry->Node.Next )\r
334     {\r
335         /* Has the menu been marked as active, maybe? */\r
336         if( menuEntry->IsActive )\r
337         {\r
338             /*\r
339              * That's truly right, and we need to have it highlighted.\r
340              * There is an assumption that mouse cursor didn't move\r
341              * since the last check of menu activity state:\r
342              */\r
343             int menuID = menuEntry->Ordinal;\r
344 \r
345             /* So have the highlight drawn... */\r
346             glColor4fv( menu_pen_hback );\r
347             glBegin( GL_QUADS );\r
348                 glVertex2i( border,\r
349                             (menuID + 0)*FREEGLUT_MENU_HEIGHT + border );\r
350                 glVertex2i( menu->Width - border,\r
351                             (menuID + 0)*FREEGLUT_MENU_HEIGHT + border );\r
352                 glVertex2i( menu->Width - border,\r
353                             (menuID + 1)*FREEGLUT_MENU_HEIGHT + border );\r
354                 glVertex2i( border,\r
355                             (menuID + 1)*FREEGLUT_MENU_HEIGHT + border );\r
356             glEnd( );\r
357         }\r
358     }\r
359 \r
360     /* Print the menu entries now... */\r
361 \r
362     glColor4fv( menu_pen_fore );\r
363 \r
364     for( menuEntry = (SFG_MenuEntry *)menu->Entries.First, i = 0;\r
365          menuEntry;\r
366          menuEntry = (SFG_MenuEntry *)menuEntry->Node.Next, ++i )\r
367     {\r
368         /* If the menu entry is active, set the color to white */\r
369         if( menuEntry->IsActive )\r
370             glColor4fv( menu_pen_hfore );\r
371 \r
372         /* Move the raster into position... */\r
373         /* Try to center the text - JCJ 31 July 2003*/\r
374         glRasterPos2i(\r
375             2 * border,\r
376             ( i + 1 )*FREEGLUT_MENU_HEIGHT -\r
377             ( int )( FREEGLUT_MENU_HEIGHT*0.3 - border )\r
378         );\r
379 \r
380         /* Have the label drawn, character after character: */\r
381         glutBitmapString( FREEGLUT_MENU_FONT,\r
382                           (unsigned char *)menuEntry->Text);\r
383 \r
384         /* If it's a submenu, draw a right arrow */\r
385         if( menuEntry->SubMenu )\r
386         {\r
387             int width = glutBitmapWidth( FREEGLUT_MENU_FONT, '_' );\r
388             int x_base = menu->Width - 2 - width;\r
389             int y_base = i*FREEGLUT_MENU_HEIGHT + border;\r
390             glBegin( GL_TRIANGLES );\r
391                 glVertex2i( x_base, y_base + 2*border);\r
392                 glVertex2i( menu->Width - 2, y_base +\r
393                             ( FREEGLUT_MENU_HEIGHT + border) / 2 );\r
394                 glVertex2i( x_base, y_base + FREEGLUT_MENU_HEIGHT - border );\r
395             glEnd( );\r
396         }\r
397 \r
398         /* If the menu entry is active, reset the color */\r
399         if( menuEntry->IsActive )\r
400             glColor4fv( menu_pen_fore );\r
401     }\r
402 }\r
403 \r
404 /*\r
405  * Private static function to set the parent window of a submenu and all\r
406  * of its submenus\r
407  */\r
408 static void fghSetMenuParentWindow( SFG_Window *window, SFG_Menu *menu )\r
409 {\r
410     SFG_MenuEntry *menuEntry;\r
411 \r
412     menu->ParentWindow = window;\r
413 \r
414     for( menuEntry = ( SFG_MenuEntry * )menu->Entries.First;\r
415          menuEntry;\r
416          menuEntry = ( SFG_MenuEntry * )menuEntry->Node.Next )\r
417         if( menuEntry->SubMenu )\r
418             fghSetMenuParentWindow( window, menuEntry->SubMenu );\r
419 }\r
420 \r
421 /*\r
422  * Function to check for menu entry selection on menu deactivation\r
423  */\r
424 static void fghExecuteMenuCallback( SFG_Menu* menu )\r
425 {\r
426     SFG_MenuEntry *menuEntry;\r
427 \r
428     /* First of all check any of the active sub menus... */\r
429     for( menuEntry = (SFG_MenuEntry *)menu->Entries.First;\r
430          menuEntry;\r
431          menuEntry = (SFG_MenuEntry *)menuEntry->Node.Next)\r
432     {\r
433         if( menuEntry->IsActive )\r
434         {\r
435             if( menuEntry->SubMenu )\r
436                 fghExecuteMenuCallback( menuEntry->SubMenu );\r
437             else\r
438                 if( menu->Callback )\r
439                 {\r
440                     SFG_Menu *save_menu = fgStructure.CurrentMenu;\r
441                     fgStructure.CurrentMenu = menu;\r
442                     menu->Callback( menuEntry->ID );\r
443                     fgStructure.CurrentMenu = save_menu;\r
444                 }\r
445 \r
446             return;\r
447         }\r
448     }\r
449 }\r
450 \r
451 \r
452 /*\r
453  * Displays the currently active menu for the current window\r
454  */\r
455 void fgDisplayMenu( void )\r
456 {\r
457     SFG_Window* window = fgStructure.CurrentWindow;\r
458     SFG_Menu* menu = NULL;\r
459 \r
460     FREEGLUT_INTERNAL_ERROR_EXIT ( fgStructure.CurrentWindow, "Displaying menu in nonexistent window",\r
461                                    "fgDisplayMenu" );\r
462 \r
463     /* Check if there is an active menu attached to this window... */\r
464     menu = window->ActiveMenu;\r
465     freeglut_return_if_fail( menu );\r
466 \r
467     fgSetWindow( menu->Window );\r
468 \r
469     glPushAttrib( GL_DEPTH_BUFFER_BIT | GL_TEXTURE_BIT | GL_LIGHTING_BIT |\r
470                   GL_POLYGON_BIT );\r
471 \r
472     glDisable( GL_DEPTH_TEST );\r
473     glDisable( GL_TEXTURE_2D );\r
474     glDisable( GL_LIGHTING   );\r
475     glDisable( GL_CULL_FACE  );\r
476 \r
477     glMatrixMode( GL_PROJECTION );\r
478     glPushMatrix( );\r
479     glLoadIdentity( );\r
480     glOrtho(\r
481          0, glutGet( GLUT_WINDOW_WIDTH  ),\r
482          glutGet( GLUT_WINDOW_HEIGHT ), 0,\r
483         -1, 1\r
484     );\r
485 \r
486     glMatrixMode( GL_MODELVIEW );\r
487     glPushMatrix( );\r
488     glLoadIdentity( );\r
489 \r
490     fghDisplayMenuBox( menu );\r
491 \r
492     glPopAttrib( );\r
493 \r
494     glMatrixMode( GL_PROJECTION );\r
495     glPopMatrix( );\r
496     glMatrixMode( GL_MODELVIEW );\r
497     glPopMatrix( );\r
498 \r
499     glutSwapBuffers( );\r
500 \r
501     fgSetWindow ( window );\r
502 }\r
503 \r
504 /*\r
505  * Activates a menu pointed by the function argument\r
506  */\r
507 static void fghActivateMenu( SFG_Window* window, int button )\r
508 {\r
509     int max_x, max_y;\r
510 \r
511     /* We'll be referencing this menu a lot, so remember its address: */\r
512     SFG_Menu* menu = window->Menu[ button ];\r
513     SFG_Window* current_window = fgStructure.CurrentWindow;\r
514 \r
515     /* If the menu is already active in another window, deactivate it there */\r
516     if ( menu->ParentWindow )\r
517       menu->ParentWindow->ActiveMenu = NULL ;\r
518 \r
519     /* Mark the menu as active, so that it gets displayed: */\r
520     window->ActiveMenu = menu;\r
521     menu->IsActive = GL_TRUE;\r
522     fghSetMenuParentWindow ( window, menu );\r
523     fgState.ActiveMenus++;\r
524 \r
525     /* Set up the initial menu position now: */\r
526     fghGetVMaxExtent(menu->ParentWindow, &max_x, &max_y);\r
527     fgSetWindow( window );\r
528     menu->X = window->State.MouseX + glutGet( GLUT_WINDOW_X );\r
529     menu->Y = window->State.MouseY + glutGet( GLUT_WINDOW_Y );\r
530 \r
531     if( menu->X + menu->Width > max_x )\r
532         menu->X -=menu->Width;\r
533 \r
534     if( menu->Y + menu->Height > max_y )\r
535     {\r
536         menu->Y -=menu->Height;\r
537         if( menu->Y < 0 )\r
538             menu->Y = 0;\r
539     }\r
540 \r
541     menu->Window->State.MouseX =\r
542         window->State.MouseX + glutGet( GLUT_WINDOW_X ) - menu->X;\r
543     menu->Window->State.MouseY =\r
544         window->State.MouseY + glutGet( GLUT_WINDOW_Y ) - menu->Y;\r
545 \r
546     fgSetWindow( menu->Window );\r
547     glutPositionWindow( menu->X, menu->Y );\r
548     glutReshapeWindow( menu->Width, menu->Height );\r
549     glutPopWindow( );\r
550     glutShowWindow( );\r
551     menu->Window->ActiveMenu = menu;\r
552     fghCheckMenuStatus( menu );\r
553     fgSetWindow( current_window );\r
554 }\r
555 \r
556 /*\r
557  * Update Highlight states of the menu\r
558  *\r
559  * Current mouse position is in menu->Window->State.MouseX/Y.\r
560  */\r
561 void fgUpdateMenuHighlight ( SFG_Menu *menu )\r
562 {\r
563     fghCheckMenuStatus( menu );\r
564 }\r
565 \r
566 /*\r
567  * Check whether an active menu absorbs a mouse click\r
568  */\r
569 GLboolean fgCheckActiveMenu ( SFG_Window *window, int button, GLboolean pressed,\r
570                               int mouse_x, int mouse_y )\r
571 {\r
572     /*\r
573      * Near as I can tell, this is the menu behaviour:\r
574      *  - Down-click the menu button, menu not active:  activate\r
575      *    the menu with its upper left-hand corner at the mouse\r
576      *    location.\r
577      *  - Down-click any button outside the menu, menu active:\r
578      *    deactivate the menu\r
579      *  - Down-click any button inside the menu, menu active:\r
580      *    select the menu entry and deactivate the menu\r
581      *  - Up-click the menu button, menu not active:  nothing happens\r
582      *  - Up-click the menu button outside the menu, menu active:\r
583      *    nothing happens\r
584      *  - Up-click the menu button inside the menu, menu active:\r
585      *    select the menu entry and deactivate the menu\r
586      * Since menus can have submenus, we need to check this recursively.\r
587      */\r
588     if( window->ActiveMenu )\r
589     {\r
590         if( window == window->ActiveMenu->ParentWindow )\r
591         {\r
592             window->ActiveMenu->Window->State.MouseX =\r
593                                        mouse_x - window->ActiveMenu->X;\r
594             window->ActiveMenu->Window->State.MouseY =\r
595                                        mouse_y - window->ActiveMenu->Y;\r
596         }\r
597 \r
598         /* In the menu, invoke the callback and deactivate the menu */\r
599         if( fghCheckMenuStatus( window->ActiveMenu ) )\r
600         {\r
601             /*\r
602              * Save the current window and menu and set the current\r
603              * window to the window whose menu this is\r
604              */\r
605             SFG_Window *save_window = fgStructure.CurrentWindow;\r
606             SFG_Menu *save_menu = fgStructure.CurrentMenu;\r
607             SFG_Window *parent_window = window->ActiveMenu->ParentWindow;\r
608             fgSetWindow( parent_window );\r
609             fgStructure.CurrentMenu = window->ActiveMenu;\r
610 \r
611             /* Execute the menu callback */\r
612             fghExecuteMenuCallback( window->ActiveMenu );\r
613             fgDeactivateMenu( parent_window );\r
614 \r
615             /* Restore the current window and menu */\r
616             fgSetWindow( save_window );\r
617             fgStructure.CurrentMenu = save_menu;\r
618         }\r
619         else if( pressed )\r
620             /*\r
621              * Outside the menu, deactivate if it's a downclick\r
622              *\r
623              * XXX This isn't enough.  A downclick outside of\r
624              * XXX the interior of our freeglut windows should also\r
625              * XXX deactivate the menu.  This is more complicated.\r
626              */\r
627             fgDeactivateMenu( window->ActiveMenu->ParentWindow );\r
628 \r
629         /*\r
630          * XXX Why does an active menu require a redisplay at\r
631          * XXX this point?  If this can come out cleanly, then\r
632          * XXX it probably should do so; if not, a comment should\r
633          * XXX explain it.\r
634          */\r
635         if( ! window->IsMenu )\r
636             window->State.Redisplay = GL_TRUE;\r
637 \r
638         return GL_TRUE;\r
639     }\r
640 \r
641     /* No active menu, let's check whether we need to activate one. */\r
642     if( ( 0 <= button ) &&\r
643         ( FREEGLUT_MAX_MENUS > button ) &&\r
644         ( window->Menu[ button ] ) &&\r
645         pressed )\r
646     {\r
647         /* XXX Posting a requisite Redisplay seems bogus. */\r
648         window->State.Redisplay = GL_TRUE;\r
649         fghActivateMenu( window, button );\r
650         return GL_TRUE;\r
651     }\r
652 \r
653     return GL_FALSE;\r
654 }\r
655 \r
656 /*\r
657  * Deactivates a menu pointed by the function argument.\r
658  */\r
659 void fgDeactivateMenu( SFG_Window *window )\r
660 {\r
661     SFG_Window *parent_window = NULL;\r
662 \r
663     /* Check if there is an active menu attached to this window... */\r
664     SFG_Menu* menu = window->ActiveMenu;\r
665     SFG_MenuEntry *menuEntry;\r
666 \r
667     /* Did we find an active window? */\r
668     freeglut_return_if_fail( menu );\r
669 \r
670     parent_window = menu->ParentWindow;\r
671 \r
672     /* Hide the present menu's window */\r
673     fgSetWindow( menu->Window );\r
674     glutHideWindow( );\r
675 \r
676     /* Forget about having that menu active anymore, now: */\r
677     menu->Window->ActiveMenu = NULL;\r
678     menu->ParentWindow->ActiveMenu = NULL;\r
679     fghSetMenuParentWindow ( NULL, menu );\r
680     menu->IsActive = GL_FALSE;\r
681     menu->ActiveEntry = NULL;\r
682 \r
683     fgState.ActiveMenus--;\r
684 \r
685     /* Hide all submenu windows, and the root menu's window. */\r
686     for ( menuEntry = ( SFG_MenuEntry * )menu->Entries.First;\r
687           menuEntry;\r
688           menuEntry = ( SFG_MenuEntry * )menuEntry->Node.Next )\r
689     {\r
690         menuEntry->IsActive = GL_FALSE;\r
691 \r
692         /* Is that an active submenu by any case? */\r
693         if( menuEntry->SubMenu )\r
694             fghDeactivateSubMenu( menuEntry );\r
695     }\r
696 \r
697     fgSetWindow ( parent_window ) ;\r
698 }\r
699 \r
700 /*\r
701  * Recalculates current menu's box size\r
702  */\r
703 void fghCalculateMenuBoxSize( void )\r
704 {\r
705     SFG_MenuEntry* menuEntry;\r
706     int width = 0, height = 0;\r
707 \r
708     /* Make sure there is a current menu set */\r
709     freeglut_return_if_fail( fgStructure.CurrentMenu );\r
710 \r
711     /* The menu's box size depends on the menu entries: */\r
712     for( menuEntry = ( SFG_MenuEntry * )fgStructure.CurrentMenu->Entries.First;\r
713          menuEntry;\r
714          menuEntry = ( SFG_MenuEntry * )menuEntry->Node.Next )\r
715     {\r
716         /* Update the menu entry's width value */\r
717         menuEntry->Width = glutBitmapLength(\r
718             FREEGLUT_MENU_FONT,\r
719             (unsigned char *)menuEntry->Text\r
720         );\r
721 \r
722         /*\r
723          * If the entry is a submenu, then it needs to be wider to\r
724          * accomodate the arrow. JCJ 31 July 2003\r
725          */\r
726         if (menuEntry->SubMenu )\r
727             menuEntry->Width += glutBitmapLength(\r
728                 FREEGLUT_MENU_FONT,\r
729                 (unsigned char *)"_"\r
730             );\r
731 \r
732         /* Check if it's the biggest we've found */\r
733         if( menuEntry->Width > width )\r
734             width = menuEntry->Width;\r
735 \r
736         height += FREEGLUT_MENU_HEIGHT;\r
737     }\r
738 \r
739     /* Store the menu's box size now: */\r
740     fgStructure.CurrentMenu->Height = height + 2 * FREEGLUT_MENU_BORDER;\r
741     fgStructure.CurrentMenu->Width  = width  + 4 * FREEGLUT_MENU_BORDER;\r
742 }\r
743 \r
744 \r
745 /* -- INTERFACE FUNCTIONS -------------------------------------------------- */\r
746 \r
747 /*\r
748  * Creates a new menu object, adding it to the freeglut structure\r
749  */\r
750 int FGAPIENTRY glutCreateMenu( void(* callback)( int ) )\r
751 {\r
752     /* The menu object creation code resides in freeglut_structure.c */\r
753     FREEGLUT_EXIT_IF_NOT_INITIALISED ( "glutCreateMenu" );\r
754     return fgCreateMenu( callback )->ID;\r
755 }\r
756 \r
757 /*\r
758  * Destroys a menu object, removing all references to it\r
759  */\r
760 void FGAPIENTRY glutDestroyMenu( int menuID )\r
761 {\r
762     SFG_Menu* menu;\r
763 \r
764     FREEGLUT_EXIT_IF_NOT_INITIALISED ( "glutDestroyMenu" );\r
765     menu = fgMenuByID( menuID );\r
766 \r
767     freeglut_return_if_fail( menu );\r
768 \r
769     /* The menu object destruction code resides in freeglut_structure.c */\r
770     fgDestroyMenu( menu );\r
771 }\r
772 \r
773 /*\r
774  * Returns the ID number of the currently active menu\r
775  */\r
776 int FGAPIENTRY glutGetMenu( void )\r
777 {\r
778     FREEGLUT_EXIT_IF_NOT_INITIALISED ( "glutGetMenu" );\r
779 \r
780     if( fgStructure.CurrentMenu )\r
781         return fgStructure.CurrentMenu->ID;\r
782 \r
783     return 0;\r
784 }\r
785 \r
786 /*\r
787  * Sets the current menu given its menu ID\r
788  */\r
789 void FGAPIENTRY glutSetMenu( int menuID )\r
790 {\r
791     SFG_Menu* menu;\r
792 \r
793     FREEGLUT_EXIT_IF_NOT_INITIALISED ( "glutSetMenu" );\r
794     menu = fgMenuByID( menuID );\r
795 \r
796     freeglut_return_if_fail( menu );\r
797 \r
798     fgStructure.CurrentMenu = menu;\r
799 }\r
800 \r
801 /*\r
802  * Adds a menu entry to the bottom of the current menu\r
803  */\r
804 void FGAPIENTRY glutAddMenuEntry( const char* label, int value )\r
805 {\r
806     SFG_MenuEntry* menuEntry;\r
807     FREEGLUT_EXIT_IF_NOT_INITIALISED ( "glutAddMenuEntry" );\r
808     menuEntry = (SFG_MenuEntry *)calloc( sizeof(SFG_MenuEntry), 1 );\r
809     freeglut_return_if_fail( fgStructure.CurrentMenu );\r
810 \r
811     menuEntry->Text = strdup( label );\r
812     menuEntry->ID   = value;\r
813 \r
814     /* Have the new menu entry attached to the current menu */\r
815     fgListAppend( &fgStructure.CurrentMenu->Entries, &menuEntry->Node );\r
816 \r
817     fghCalculateMenuBoxSize( );\r
818 }\r
819 \r
820 /*\r
821  * Add a sub menu to the bottom of the current menu\r
822  */\r
823 void FGAPIENTRY glutAddSubMenu( const char *label, int subMenuID )\r
824 {\r
825     SFG_MenuEntry *menuEntry;\r
826     SFG_Menu *subMenu;\r
827 \r
828     FREEGLUT_EXIT_IF_NOT_INITIALISED ( "glutAddSubMenu" );\r
829     menuEntry = ( SFG_MenuEntry * )calloc( sizeof( SFG_MenuEntry ), 1 );\r
830     subMenu = fgMenuByID( subMenuID );\r
831 \r
832     freeglut_return_if_fail( fgStructure.CurrentMenu );\r
833     freeglut_return_if_fail( subMenu );\r
834 \r
835     menuEntry->Text    = strdup( label );\r
836     menuEntry->SubMenu = subMenu;\r
837     menuEntry->ID      = -1;\r
838 \r
839     fgListAppend( &fgStructure.CurrentMenu->Entries, &menuEntry->Node );\r
840     fghCalculateMenuBoxSize( );\r
841 }\r
842 \r
843 /*\r
844  * Changes the specified menu item in the current menu into a menu entry\r
845  */\r
846 void FGAPIENTRY glutChangeToMenuEntry( int item, const char* label, int value )\r
847 {\r
848     SFG_MenuEntry* menuEntry = NULL;\r
849 \r
850     FREEGLUT_EXIT_IF_NOT_INITIALISED ( "glutChangeToMenuEntry" );\r
851     freeglut_return_if_fail( fgStructure.CurrentMenu );\r
852 \r
853     /* Get n-th menu entry in the current menu, starting from one: */\r
854     menuEntry = fghFindMenuEntry( fgStructure.CurrentMenu, item );\r
855 \r
856     freeglut_return_if_fail( menuEntry );\r
857 \r
858     /* We want it to become a normal menu entry, so: */\r
859     if( menuEntry->Text )\r
860         free( menuEntry->Text );\r
861 \r
862     menuEntry->Text    = strdup( label );\r
863     menuEntry->ID      = value;\r
864     menuEntry->SubMenu = NULL;\r
865     fghCalculateMenuBoxSize( );\r
866 }\r
867 \r
868 /*\r
869  * Changes the specified menu item in the current menu into a sub-menu trigger.\r
870  */\r
871 void FGAPIENTRY glutChangeToSubMenu( int item, const char* label,\r
872                                      int subMenuID )\r
873 {\r
874     SFG_Menu*      subMenu;\r
875     SFG_MenuEntry* menuEntry;\r
876 \r
877     FREEGLUT_EXIT_IF_NOT_INITIALISED ( "glutChangeToSubMenu" );\r
878     subMenu = fgMenuByID( subMenuID );\r
879     menuEntry = NULL;\r
880 \r
881     freeglut_return_if_fail( fgStructure.CurrentMenu );\r
882     freeglut_return_if_fail( subMenu );\r
883 \r
884     /* Get n-th menu entry in the current menu, starting from one: */\r
885     menuEntry = fghFindMenuEntry( fgStructure.CurrentMenu, item );\r
886 \r
887     freeglut_return_if_fail( menuEntry );\r
888 \r
889     /* We want it to become a sub menu entry, so: */\r
890     if( menuEntry->Text )\r
891         free( menuEntry->Text );\r
892 \r
893     menuEntry->Text    = strdup( label );\r
894     menuEntry->SubMenu = subMenu;\r
895     menuEntry->ID      = -1;\r
896     fghCalculateMenuBoxSize( );\r
897 }\r
898 \r
899 /*\r
900  * Removes the specified menu item from the current menu\r
901  */\r
902 void FGAPIENTRY glutRemoveMenuItem( int item )\r
903 {\r
904     SFG_MenuEntry* menuEntry;\r
905 \r
906     FREEGLUT_EXIT_IF_NOT_INITIALISED ( "glutRemoveMenuItem" );\r
907     freeglut_return_if_fail( fgStructure.CurrentMenu );\r
908 \r
909     /* Get n-th menu entry in the current menu, starting from one: */\r
910     menuEntry = fghFindMenuEntry( fgStructure.CurrentMenu, item );\r
911 \r
912     freeglut_return_if_fail( menuEntry );\r
913 \r
914     fgListRemove( &fgStructure.CurrentMenu->Entries, &menuEntry->Node );\r
915     if ( menuEntry->Text )\r
916       free( menuEntry->Text );\r
917 \r
918     free( menuEntry );\r
919     fghCalculateMenuBoxSize( );\r
920 }\r
921 \r
922 /*\r
923  * Attaches a menu to the current window\r
924  */\r
925 void FGAPIENTRY glutAttachMenu( int button )\r
926 {\r
927     FREEGLUT_EXIT_IF_NOT_INITIALISED ( "glutAttachMenu" );\r
928 \r
929     freeglut_return_if_fail( fgStructure.CurrentWindow );\r
930     freeglut_return_if_fail( fgStructure.CurrentMenu );\r
931 \r
932     freeglut_return_if_fail( button >= 0 );\r
933     freeglut_return_if_fail( button < FREEGLUT_MAX_MENUS );\r
934 \r
935     fgStructure.CurrentWindow->Menu[ button ] = fgStructure.CurrentMenu;\r
936 }\r
937 \r
938 /*\r
939  * Detaches a menu from the current window\r
940  */\r
941 void FGAPIENTRY glutDetachMenu( int button )\r
942 {\r
943     FREEGLUT_EXIT_IF_NOT_INITIALISED ( "glutDetachMenu" );\r
944 \r
945     freeglut_return_if_fail( fgStructure.CurrentWindow );\r
946     freeglut_return_if_fail( fgStructure.CurrentMenu );\r
947 \r
948     freeglut_return_if_fail( button >= 0 );\r
949     freeglut_return_if_fail( button < FREEGLUT_MAX_MENUS );\r
950 \r
951     fgStructure.CurrentWindow->Menu[ button ] = NULL;\r
952 }\r
953 \r
954 /*\r
955  * A.Donev: Set and retrieve the menu's user data\r
956  */\r
957 void* FGAPIENTRY glutGetMenuData( void )\r
958 {\r
959     FREEGLUT_EXIT_IF_NOT_INITIALISED ( "glutGetMenuData" );\r
960     return fgStructure.CurrentMenu->UserData;\r
961 }\r
962 \r
963 void FGAPIENTRY glutSetMenuData(void* data)\r
964 {\r
965     FREEGLUT_EXIT_IF_NOT_INITIALISED ( "glutSetMenuData" );\r
966     fgStructure.CurrentMenu->UserData=data;\r
967 }\r
968 \r
969 /*** END OF FILE ***/\r