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