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