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