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