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