Trying again to change \"fgStructure.Window\" to \"fgStructure.CurrentWindow\" and...
[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  * Deactivates a menu pointed by the function argument.
110  */
111 static void fghDeactivateSubMenu( SFG_MenuEntry *menuEntry )
112 {
113     SFG_Window *current_window = fgStructure.CurrentWindow;
114     SFG_MenuEntry *subMenuIter;
115     /* Hide the present menu's window */
116     fgSetWindow( menuEntry->SubMenu->Window );
117     glutHideWindow( );
118
119     /* Forget about having that menu active anymore, now: */
120     menuEntry->SubMenu->Window->ActiveMenu = NULL;
121     menuEntry->SubMenu->IsActive = GL_FALSE;
122
123     /* Hide all submenu windows, and the root menu's window. */
124     for ( subMenuIter = (SFG_MenuEntry *)menuEntry->SubMenu->Entries.First;
125           subMenuIter;
126           subMenuIter = (SFG_MenuEntry *)subMenuIter->Node.Next )
127     {
128         /* Is that an active submenu by any case? */
129         if( subMenuIter->SubMenu )
130             fghDeactivateSubMenu( subMenuIter );
131     }
132
133     fgSetWindow( current_window );
134 }
135
136 /*
137  * Private function to check for the current menu/sub menu activity state
138  */
139 static GLboolean fghCheckMenuStatus( SFG_Window* window, SFG_Menu* menu )
140 {
141     SFG_MenuEntry* menuEntry;
142     int x, y;
143
144     /* First of all check any of the active sub menus... */
145     for( menuEntry = (SFG_MenuEntry *)menu->Entries.First;
146          menuEntry;
147          menuEntry = (SFG_MenuEntry *)menuEntry->Node.Next )
148     {
149         if( menuEntry->SubMenu && menuEntry->IsActive )
150         {
151             /*
152              * OK, have the sub-menu checked, too. If it returns GL_TRUE, it
153              * will mean that it caught the mouse cursor and we do not need
154              * to regenerate the activity list, and so our parents do...
155              */
156             GLboolean return_status = fghCheckMenuStatus( window,
157                                                           menuEntry->SubMenu );
158
159             /*
160              * Reactivate the submenu as the checkMenuStatus may have turned
161              * it off if the mouse is in its parent menu entry.
162              */
163             menuEntry->SubMenu->IsActive = GL_TRUE;
164             if ( return_status )
165                 return GL_TRUE;
166         }
167     }
168
169     /* That much about our sub menus, let's get to checking the current menu: */
170     x = window->State.MouseX;
171     y = window->State.MouseY;
172
173     for( menuEntry = (SFG_MenuEntry *)menu->Entries.First;
174          menuEntry;
175          menuEntry = (SFG_MenuEntry *)menuEntry->Node.Next )
176         menuEntry->IsActive = GL_FALSE;
177
178     menu->IsActive = GL_FALSE;
179
180     /* Check if the mouse cursor is contained within the current menu box */
181     if( ( x >= FREEGLUT_MENU_BORDER ) &&
182         ( x < menu->Width  - FREEGLUT_MENU_BORDER ) &&
183         ( y >= FREEGLUT_MENU_BORDER ) &&
184         ( y < menu->Height - FREEGLUT_MENU_BORDER ) &&
185         ( window == menu->Window ) )
186     {
187         int menuID = ( y - FREEGLUT_MENU_BORDER ) / FREEGLUT_MENU_HEIGHT;
188
189         /* The mouse cursor is somewhere over our box, check it out. */
190         menuEntry = fghFindMenuEntry( menu, menuID + 1 );
191         FREEGLUT_INTERNAL_ERROR_EXIT( menuEntry, "Cannot find menu entry",
192                                       "fghCheckMenuStatus" );
193
194         menuEntry->IsActive = GL_TRUE;
195         menuEntry->Ordinal = menuID;
196
197         /*
198          * If this is not the same as the last active menu entry, deactivate
199          * the previous entry.  Specifically, if the previous active entry
200          * was a submenu then deactivate it.
201          */
202         if( menu->ActiveEntry && ( menuEntry != menu->ActiveEntry ) )
203             if( menu->ActiveEntry->SubMenu )
204                 fghDeactivateSubMenu( menu->ActiveEntry );
205
206         menu->ActiveEntry = menuEntry;
207         menu->IsActive = GL_TRUE;
208
209         /*
210          * OKi, we have marked that entry as active, but it would be also
211          * nice to have its contents updated, in case it's a sub menu.
212          * Also, ignore the return value of the check function:
213          */
214         if( menuEntry->SubMenu )
215         {
216             if ( ! menuEntry->SubMenu->IsActive )
217             {
218                 SFG_Window *current_window = fgStructure.CurrentWindow;
219
220                 /* Set up the initial menu position now... */
221                 menuEntry->SubMenu->IsActive = GL_TRUE;
222
223                 /* Set up the initial submenu position now: */
224                 menuEntry->SubMenu->X = menu->X + menu->Width;
225                 menuEntry->SubMenu->Y = menu->Y +
226                     menuEntry->Ordinal * FREEGLUT_MENU_HEIGHT;
227
228                 if( menuEntry->SubMenu->X + menuEntry->SubMenu->Width >
229                     glutGet( GLUT_SCREEN_WIDTH ) )
230                     menuEntry->SubMenu->X = menu->X -
231                         menuEntry->SubMenu->Width;
232
233                 if( menuEntry->SubMenu->Y + menuEntry->SubMenu->Height >
234                     glutGet( GLUT_SCREEN_HEIGHT ) )
235                     menuEntry->SubMenu->Y -= ( menuEntry->SubMenu->Height -
236                                                FREEGLUT_MENU_HEIGHT -
237                                                2 * FREEGLUT_MENU_BORDER );
238
239                 fgSetWindow( menuEntry->SubMenu->Window );
240                 glutPositionWindow( menuEntry->SubMenu->X,
241                                     menuEntry->SubMenu->Y );
242                 glutReshapeWindow( menuEntry->SubMenu->Width,
243                                    menuEntry->SubMenu->Height );
244                 glutPopWindow( );
245                 glutShowWindow( );
246                 menuEntry->SubMenu->Window->ActiveMenu = menuEntry->SubMenu;
247                 fgSetWindow( current_window );
248             }
249
250             fghCheckMenuStatus( window, menuEntry->SubMenu );
251
252             /* Activate it because its parent entry is active */
253             menuEntry->SubMenu->IsActive = GL_TRUE;
254         }
255
256         /* Report back that we have caught the menu cursor */
257         return GL_TRUE;
258     }
259
260     /* Looks like the menu cursor is somewhere else... */
261     return GL_FALSE;
262 }
263
264 /*
265  * Displays a menu box and all of its submenus (if they are active)
266  */
267 static void fghDisplayMenuBox( SFG_Menu* menu )
268 {
269     SFG_MenuEntry *menuEntry;
270     int i;
271     int border = FREEGLUT_MENU_BORDER;
272
273     /*
274      * Have the menu box drawn first. The +- values are
275      * here just to make it more nice-looking...
276      */
277     /* a non-black dark version of the below. */
278     glColor4f( 1.0f, 1.0f, 1.0f, 1.0f );
279     glBegin( GL_QUAD_STRIP );
280         glVertex2i( menu->Width         , 0                    );
281         glVertex2i( menu->Width - border,                border);
282         glVertex2i( 0                   , 0                    );
283         glVertex2i(               border,                border);
284         glVertex2i( 0                   , menu->Height         );
285         glVertex2i(               border, menu->Height - border);
286     glEnd( );
287
288     /* a non-black dark version of the below. */
289     glColor4f( 0.5f, 0.5f, 0.5f, 1.0f );
290     glBegin( GL_QUAD_STRIP );
291         glVertex2i( 0                   , menu->Height         );
292         glVertex2i(               border, menu->Height - border);
293         glVertex2i( menu->Width         , menu->Height         );
294         glVertex2i( menu->Width - border, menu->Height - border);
295         glVertex2i( menu->Width         , 0                    );
296         glVertex2i( menu->Width - border,                border);
297     glEnd( );
298
299     glColor4fv( menu_pen_back );
300     glBegin( GL_QUADS );
301         glVertex2i(               border,                border);
302         glVertex2i( menu->Width - border,                border);
303         glVertex2i( menu->Width - border, menu->Height - border);
304         glVertex2i(               border, menu->Height - border);
305     glEnd( );
306
307     /* Check if any of the submenus is currently active... */
308     for( menuEntry = (SFG_MenuEntry *)menu->Entries.First;
309          menuEntry;
310          menuEntry = (SFG_MenuEntry *)menuEntry->Node.Next )
311     {
312         /* Has the menu been marked as active, maybe? */
313         if( menuEntry->IsActive )
314         {
315             /*
316              * That's truly right, and we need to have it highlighted.
317              * There is an assumption that mouse cursor didn't move
318              * since the last check of menu activity state:
319              */
320             int menuID = menuEntry->Ordinal;
321
322             /* So have the highlight drawn... */
323             glColor4fv( menu_pen_hback );
324             glBegin( GL_QUADS );
325                 glVertex2i( border,
326                             (menuID + 0)*FREEGLUT_MENU_HEIGHT + border );
327                 glVertex2i( menu->Width - border,
328                             (menuID + 0)*FREEGLUT_MENU_HEIGHT + border );
329                 glVertex2i( menu->Width - border,
330                             (menuID + 1)*FREEGLUT_MENU_HEIGHT + border );
331                 glVertex2i( border,
332                             (menuID + 1)*FREEGLUT_MENU_HEIGHT + border );
333             glEnd( );
334         }
335     }
336
337     /* Print the menu entries now... */
338
339     glColor4fv( menu_pen_fore );
340
341     for( menuEntry = (SFG_MenuEntry *)menu->Entries.First, i = 0;
342          menuEntry;
343          menuEntry = (SFG_MenuEntry *)menuEntry->Node.Next, ++i )
344     {
345         /* If the menu entry is active, set the color to white */
346         if( menuEntry->IsActive )
347             glColor4fv( menu_pen_hfore );
348
349         /* Move the raster into position... */
350         /* Try to center the text - JCJ 31 July 2003*/
351         glRasterPos2i(
352             2 * border,
353             ( i + 1 )*FREEGLUT_MENU_HEIGHT -
354             ( int )( FREEGLUT_MENU_HEIGHT*0.3 - border )
355         );
356
357         /* Have the label drawn, character after character: */
358         glutBitmapString( FREEGLUT_MENU_FONT,
359                           (unsigned char *)menuEntry->Text);
360
361         /* If it's a submenu, draw a right arrow */
362         if( menuEntry->SubMenu )
363         {
364             int width = glutBitmapWidth( FREEGLUT_MENU_FONT, '_' );
365             int x_base = menu->Width - 2 - width;
366             int y_base = i*FREEGLUT_MENU_HEIGHT + border;
367             glBegin( GL_TRIANGLES );
368                 glVertex2i( x_base, y_base + 2*border);
369                 glVertex2i( menu->Width - 2, y_base +
370                             ( FREEGLUT_MENU_HEIGHT + border) / 2 );
371                 glVertex2i( x_base, y_base + FREEGLUT_MENU_HEIGHT - border );
372             glEnd( );
373         }
374
375         /* If the menu entry is active, reset the color */
376         if( menuEntry->IsActive )
377             glColor4fv( menu_pen_fore );
378     }
379
380     /* Now we are ready to check if any of our children needs to be redrawn: */
381     for( menuEntry = ( SFG_MenuEntry * )menu->Entries.First;
382          menuEntry;
383          menuEntry = ( SFG_MenuEntry * )menuEntry->Node.Next )
384     {
385         /* Is that an active sub menu by any case? */
386         if( menuEntry->SubMenu && menuEntry->IsActive )
387         {
388             /* Yeah, indeed. Have it redrawn now: */
389             fgSetWindow( menuEntry->SubMenu->Window );
390             fghDisplayMenuBox( menuEntry->SubMenu );
391             fgSetWindow( menu->Window );
392         }
393     }
394 }
395
396 /*
397  * Private static function to set the parent window of a submenu and all
398  * of its submenus
399  */
400 static void fghSetMenuParentWindow( SFG_Window *window, SFG_Menu *menu )
401 {
402     SFG_MenuEntry *menuEntry;
403
404     menu->ParentWindow = window;
405
406     for( menuEntry = ( SFG_MenuEntry * )menu->Entries.First;
407          menuEntry;
408          menuEntry = ( SFG_MenuEntry * )menuEntry->Node.Next )
409         if( menuEntry->SubMenu )
410             fghSetMenuParentWindow( window, menuEntry->SubMenu );
411 }
412
413 /*
414  * Function to check for menu entry selection on menu deactivation
415  */
416 static void fghExecuteMenuCallback( SFG_Menu* menu )
417 {
418     SFG_MenuEntry *menuEntry;
419
420     /* First of all check any of the active sub menus... */
421     for( menuEntry = (SFG_MenuEntry *)menu->Entries.First;
422          menuEntry;
423          menuEntry = (SFG_MenuEntry *)menuEntry->Node.Next)
424     {
425         if( menuEntry->IsActive )
426         {
427             if( menuEntry->SubMenu )
428                 fghExecuteMenuCallback( menuEntry->SubMenu );
429             else
430                 if( menu->Callback )
431                     menu->Callback( menuEntry->ID );
432             return;
433         }
434     }
435 }
436
437
438 /*
439  * Displays the currently active menu for the current window
440  */
441 void fgDisplayMenu( void )
442 {
443     SFG_Window* window = fgStructure.CurrentWindow;
444     SFG_Menu* menu = NULL;
445
446     FREEGLUT_INTERNAL_ERROR_EXIT ( fgStructure.CurrentWindow, "Displaying menu in nonexistent window",
447                                    "fgDisplayMenu" );
448
449     /* Check if there is an active menu attached to this window... */
450     menu = window->ActiveMenu;
451     freeglut_return_if_fail( menu );
452
453     fgSetWindow( menu->Window );
454
455     glPushAttrib( GL_DEPTH_BUFFER_BIT | GL_TEXTURE_BIT | GL_LIGHTING_BIT |
456                   GL_POLYGON_BIT );
457
458     glDisable( GL_DEPTH_TEST );
459     glDisable( GL_TEXTURE_2D );
460     glDisable( GL_LIGHTING   );
461     glDisable( GL_CULL_FACE  );
462
463     glMatrixMode( GL_PROJECTION );
464     glPushMatrix( );
465     glLoadIdentity( );
466     glOrtho(
467          0, glutGet( GLUT_WINDOW_WIDTH  ),
468          glutGet( GLUT_WINDOW_HEIGHT ), 0,
469         -1, 1
470     );
471
472     glMatrixMode( GL_MODELVIEW );
473     glPushMatrix( );
474     glLoadIdentity( );
475
476     fghCheckMenuStatus( window, menu );
477     fghDisplayMenuBox( menu );
478
479     glPopAttrib( );
480
481     glMatrixMode( GL_PROJECTION );
482     glPopMatrix( );
483     glMatrixMode( GL_MODELVIEW );
484     glPopMatrix( );
485
486     glutSwapBuffers( );
487
488     fgSetWindow ( window );
489 }
490
491 /*
492  * Activates a menu pointed by the function argument
493  */
494 static void fghActivateMenu( SFG_Window* window, int button )
495 {
496     /* We'll be referencing this menu a lot, so remember its address: */
497     SFG_Menu* menu = window->Menu[ button ];
498
499     /* If the menu is already active in another window, deactivate it there */
500     if ( menu->ParentWindow )
501       menu->ParentWindow->ActiveMenu = NULL ;
502
503     /* Mark the menu as active, so that it gets displayed: */
504     window->ActiveMenu = menu;
505     menu->IsActive = GL_TRUE;
506     fghSetMenuParentWindow ( window, menu );
507     fgState.ActiveMenus++;
508
509     /* Set up the initial menu position now: */
510     menu->X = window->State.MouseX + glutGet( GLUT_WINDOW_X );
511     menu->Y = window->State.MouseY + glutGet( GLUT_WINDOW_Y );
512
513     if( menu->X + menu->Width > glutGet ( GLUT_SCREEN_WIDTH ) )
514         menu->X -=menu->Width;
515
516     if( menu->Y + menu->Height > glutGet ( GLUT_SCREEN_HEIGHT ) )
517         menu->Y -=menu->Height;
518
519     fgSetWindow( menu->Window );
520     glutPositionWindow( menu->X, menu->Y );
521     glutReshapeWindow( menu->Width, menu->Height );
522     glutPopWindow( );
523     glutShowWindow( );
524     menu->Window->ActiveMenu = menu;
525 }
526
527 /*
528  * Check whether an active menu absorbs a mouse click
529  */
530 GLboolean fgCheckActiveMenu ( SFG_Window *window, int button, GLboolean pressed,
531                               int mouse_x, int mouse_y )
532 {
533     /*
534      * Near as I can tell, this is the menu behaviour:
535      *  - Down-click the menu button, menu not active:  activate
536      *    the menu with its upper left-hand corner at the mouse
537      *    location.
538      *  - Down-click any button outside the menu, menu active:
539      *    deactivate the menu
540      *  - Down-click any button inside the menu, menu active:
541      *    select the menu entry and deactivate the menu
542      *  - Up-click the menu button, menu not active:  nothing happens
543      *  - Up-click the menu button outside the menu, menu active:
544      *    nothing happens
545      *  - Up-click the menu button inside the menu, menu active:
546      *    select the menu entry and deactivate the menu
547      * Since menus can have submenus, we need to check this recursively.
548      */
549     if( window->ActiveMenu )
550     {
551         if( window == window->ActiveMenu->ParentWindow )
552         {
553             window->ActiveMenu->Window->State.MouseX =
554                                        mouse_x - window->ActiveMenu->X;
555             window->ActiveMenu->Window->State.MouseY =
556                                        mouse_y - window->ActiveMenu->Y;
557         }
558
559         /* In the menu, invoke the callback and deactivate the menu */
560         if( fghCheckMenuStatus( window->ActiveMenu->Window,
561                                 window->ActiveMenu ) )
562         {
563             /*
564              * Save the current window and menu and set the current
565              * window to the window whose menu this is
566              */
567             SFG_Window *save_window = fgStructure.CurrentWindow;
568             SFG_Menu *save_menu = fgStructure.CurrentMenu;
569             SFG_Window *parent_window = window->ActiveMenu->ParentWindow;
570             fgSetWindow( parent_window );
571             fgStructure.CurrentMenu = window->ActiveMenu;
572
573             /* Execute the menu callback */
574             fghExecuteMenuCallback( window->ActiveMenu );
575             fgDeactivateMenu( parent_window );
576
577             /* Restore the current window and menu */
578             fgSetWindow( save_window );
579             fgStructure.CurrentMenu = save_menu;
580         }
581         else if( pressed )
582             /*
583              * Outside the menu, deactivate if it's a downclick
584              *
585              * XXX This isn't enough.  A downclick outside of
586              * XXX the interior of our freeglut windows should also
587              * XXX deactivate the menu.  This is more complicated.
588              */
589             fgDeactivateMenu( window->ActiveMenu->ParentWindow );
590
591         /*
592          * XXX Why does an active menu require a redisplay at
593          * XXX this point?  If this can come out cleanly, then
594          * XXX it probably should do so; if not, a comment should
595          * XXX explain it.
596          */
597         if( ! window->IsMenu )
598             window->State.Redisplay = GL_TRUE;
599
600         return GL_TRUE;
601     }
602
603     /* No active menu, let's check whether we need to activate one. */
604     if( ( 0 <= button ) &&
605         ( FREEGLUT_MAX_MENUS > button ) &&
606         ( window->Menu[ button ] ) &&
607         pressed )
608     {
609         /* XXX Posting a requisite Redisplay seems bogus. */
610         window->State.Redisplay = GL_TRUE;
611         fgSetWindow( window );
612         fghActivateMenu( window, button );
613         return GL_TRUE;
614     }
615
616     return GL_FALSE;
617 }
618
619 /*
620  * Deactivates a menu pointed by the function argument.
621  */
622 void fgDeactivateMenu( SFG_Window *window )
623 {
624     SFG_Window *current_window = fgStructure.CurrentWindow;
625
626     /* Check if there is an active menu attached to this window... */
627     SFG_Menu* menu = window->ActiveMenu;
628     SFG_MenuEntry *menuEntry;
629
630     /* Did we find an active window? */
631     freeglut_return_if_fail( menu );
632
633     /* Hide the present menu's window */
634     fgSetWindow( menu->Window );
635     glutHideWindow( );
636
637     /* Forget about having that menu active anymore, now: */
638     menu->Window->ActiveMenu = NULL;
639     menu->ParentWindow->ActiveMenu = NULL;
640     fghSetMenuParentWindow ( NULL, menu );
641     menu->IsActive = GL_FALSE;
642
643     fgState.ActiveMenus--;
644
645     /* Hide all submenu windows, and the root menu's window. */
646     for ( menuEntry = ( SFG_MenuEntry * )menu->Entries.First;
647           menuEntry;
648           menuEntry = ( SFG_MenuEntry * )menuEntry->Node.Next )
649     {
650         /* Is that an active submenu by any case? */
651         if( menuEntry->SubMenu )
652             fghDeactivateSubMenu( menuEntry );
653     }
654
655     fgSetWindow( current_window );
656 }
657
658 /*
659  * Recalculates current menu's box size
660  */
661 void fghCalculateMenuBoxSize( void )
662 {
663     SFG_MenuEntry* menuEntry;
664     int width = 0, height = 0;
665
666     /* Make sure there is a current menu set */
667     freeglut_return_if_fail( fgStructure.CurrentMenu );
668
669     /* The menu's box size depends on the menu entries: */
670     for( menuEntry = ( SFG_MenuEntry * )fgStructure.CurrentMenu->Entries.First;
671          menuEntry;
672          menuEntry = ( SFG_MenuEntry * )menuEntry->Node.Next )
673     {
674         /* Update the menu entry's width value */
675         menuEntry->Width = glutBitmapLength(
676             FREEGLUT_MENU_FONT,
677             (unsigned char *)menuEntry->Text
678         );
679
680         /*
681          * If the entry is a submenu, then it needs to be wider to
682          * accomodate the arrow. JCJ 31 July 2003
683          */
684         if (menuEntry->SubMenu )
685             menuEntry->Width += glutBitmapLength(
686                 FREEGLUT_MENU_FONT,
687                 (unsigned char *)"_"
688             );
689
690         /* Check if it's the biggest we've found */
691         if( menuEntry->Width > width )
692             width = menuEntry->Width;
693
694         height += FREEGLUT_MENU_HEIGHT;
695     }
696
697     /* Store the menu's box size now: */
698     fgStructure.CurrentMenu->Height = height + 2 * FREEGLUT_MENU_BORDER;
699     fgStructure.CurrentMenu->Width  = width  + 4 * FREEGLUT_MENU_BORDER;
700 }
701
702
703 /* -- INTERFACE FUNCTIONS -------------------------------------------------- */
704
705 /*
706  * Creates a new menu object, adding it to the freeglut structure
707  */
708 int FGAPIENTRY glutCreateMenu( void(* callback)( int ) )
709 {
710     /* The menu object creation code resides in freeglut_structure.c */
711     FREEGLUT_EXIT_IF_NOT_INITIALISED ( "glutCreateMenu" );
712     return fgCreateMenu( callback )->ID;
713 }
714
715 /*
716  * Destroys a menu object, removing all references to it
717  */
718 void FGAPIENTRY glutDestroyMenu( int menuID )
719 {
720     SFG_Menu* menu;
721
722     FREEGLUT_EXIT_IF_NOT_INITIALISED ( "glutDestroyMenu" );
723     menu = fgMenuByID( menuID );
724
725     freeglut_return_if_fail( menu );
726
727     /* The menu object destruction code resides in freeglut_structure.c */
728     fgDestroyMenu( menu );
729 }
730
731 /*
732  * Returns the ID number of the currently active menu
733  */
734 int FGAPIENTRY glutGetMenu( void )
735 {
736     FREEGLUT_EXIT_IF_NOT_INITIALISED ( "glutGetMenu" );
737
738     if( fgStructure.CurrentMenu )
739         return fgStructure.CurrentMenu->ID;
740
741     return 0;
742 }
743
744 /*
745  * Sets the current menu given its menu ID
746  */
747 void FGAPIENTRY glutSetMenu( int menuID )
748 {
749     SFG_Menu* menu;
750
751     FREEGLUT_EXIT_IF_NOT_INITIALISED ( "glutSetMenu" );
752     menu = fgMenuByID( menuID );
753
754     freeglut_return_if_fail( menu );
755
756     fgStructure.CurrentMenu = menu;
757 }
758
759 /*
760  * Adds a menu entry to the bottom of the current menu
761  */
762 void FGAPIENTRY glutAddMenuEntry( const char* label, int value )
763 {
764     SFG_MenuEntry* menuEntry;
765     FREEGLUT_EXIT_IF_NOT_INITIALISED ( "glutAddMenuEntry" );
766     menuEntry = (SFG_MenuEntry *)calloc( sizeof(SFG_MenuEntry), 1 );
767     freeglut_return_if_fail( fgStructure.CurrentMenu );
768
769     menuEntry->Text = strdup( label );
770     menuEntry->ID   = value;
771
772     /* Have the new menu entry attached to the current menu */
773     fgListAppend( &fgStructure.CurrentMenu->Entries, &menuEntry->Node );
774
775     fghCalculateMenuBoxSize( );
776 }
777
778 /*
779  * Add a sub menu to the bottom of the current menu
780  */
781 void FGAPIENTRY glutAddSubMenu( const char *label, int subMenuID )
782 {
783     SFG_MenuEntry *menuEntry;
784     SFG_Menu *subMenu;
785
786     FREEGLUT_EXIT_IF_NOT_INITIALISED ( "glutAddSubMenu" );
787     menuEntry = ( SFG_MenuEntry * )calloc( sizeof( SFG_MenuEntry ), 1 );
788     subMenu = fgMenuByID( subMenuID );
789
790     freeglut_return_if_fail( fgStructure.CurrentMenu );
791     freeglut_return_if_fail( subMenu );
792
793     menuEntry->Text    = strdup( label );
794     menuEntry->SubMenu = subMenu;
795     menuEntry->ID      = -1;
796
797     fgListAppend( &fgStructure.CurrentMenu->Entries, &menuEntry->Node );
798     fghCalculateMenuBoxSize( );
799 }
800
801 /*
802  * Changes the specified menu item in the current menu into a menu entry
803  */
804 void FGAPIENTRY glutChangeToMenuEntry( int item, const char* label, int value )
805 {
806     SFG_MenuEntry* menuEntry = NULL;
807
808     FREEGLUT_EXIT_IF_NOT_INITIALISED ( "glutChangeToMenuEntry" );
809     freeglut_return_if_fail( fgStructure.CurrentMenu );
810
811     /* Get n-th menu entry in the current menu, starting from one: */
812     menuEntry = fghFindMenuEntry( fgStructure.CurrentMenu, item );
813
814     freeglut_return_if_fail( menuEntry );
815
816     /* We want it to become a normal menu entry, so: */
817     if( menuEntry->Text )
818         free( menuEntry->Text );
819
820     menuEntry->Text    = strdup( label );
821     menuEntry->ID      = value;
822     menuEntry->SubMenu = NULL;
823     fghCalculateMenuBoxSize( );
824 }
825
826 /*
827  * Changes the specified menu item in the current menu into a sub-menu trigger.
828  */
829 void FGAPIENTRY glutChangeToSubMenu( int item, const char* label,
830                                      int subMenuID )
831 {
832     SFG_Menu*      subMenu;
833     SFG_MenuEntry* menuEntry;
834
835     FREEGLUT_EXIT_IF_NOT_INITIALISED ( "glutChangeToSubMenu" );
836     subMenu = fgMenuByID( subMenuID );
837     menuEntry = NULL;
838
839     freeglut_return_if_fail( fgStructure.CurrentMenu );
840     freeglut_return_if_fail( subMenu );
841
842     /* Get n-th menu entry in the current menu, starting from one: */
843     menuEntry = fghFindMenuEntry( fgStructure.CurrentMenu, item );
844
845     freeglut_return_if_fail( menuEntry );
846
847     /* We want it to become a sub menu entry, so: */
848     if( menuEntry->Text )
849         free( menuEntry->Text );
850
851     menuEntry->Text    = strdup( label );
852     menuEntry->SubMenu = subMenu;
853     menuEntry->ID      = -1;
854     fghCalculateMenuBoxSize( );
855 }
856
857 /*
858  * Removes the specified menu item from the current menu
859  */
860 void FGAPIENTRY glutRemoveMenuItem( int item )
861 {
862     SFG_MenuEntry* menuEntry;
863
864     FREEGLUT_EXIT_IF_NOT_INITIALISED ( "glutRemoveMenuItem" );
865     freeglut_return_if_fail( fgStructure.CurrentMenu );
866
867     /* Get n-th menu entry in the current menu, starting from one: */
868     menuEntry = fghFindMenuEntry( fgStructure.CurrentMenu, item );
869
870     freeglut_return_if_fail( menuEntry );
871
872     fgListRemove( &fgStructure.CurrentMenu->Entries, &menuEntry->Node );
873     if ( menuEntry->Text )
874       free( menuEntry->Text );
875
876     free( menuEntry );
877     fghCalculateMenuBoxSize( );
878 }
879
880 /*
881  * Attaches a menu to the current window
882  */
883 void FGAPIENTRY glutAttachMenu( int button )
884 {
885     FREEGLUT_EXIT_IF_NOT_INITIALISED ( "glutAttachMenu" );
886
887     freeglut_return_if_fail( fgStructure.CurrentWindow );
888     freeglut_return_if_fail( fgStructure.CurrentMenu );
889
890     freeglut_return_if_fail( button >= 0 );
891     freeglut_return_if_fail( button < FREEGLUT_MAX_MENUS );
892
893     fgStructure.CurrentWindow->Menu[ button ] = fgStructure.CurrentMenu;
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.CurrentWindow );
904     freeglut_return_if_fail( fgStructure.CurrentMenu );
905
906     freeglut_return_if_fail( button >= 0 );
907     freeglut_return_if_fail( button < FREEGLUT_MAX_MENUS );
908
909     fgStructure.CurrentWindow->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.CurrentMenu->UserData;
919 }
920
921 void FGAPIENTRY glutSetMenuData(void* data)
922 {
923     FREEGLUT_EXIT_IF_NOT_INITIALISED ( "glutSetMenuData" );
924     fgStructure.CurrentMenu->UserData=data;
925 }
926
927 /*** END OF FILE ***/