Minor typo fix
[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 = menu->Entries.First; entry; entry = 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 = menu->Entries.First; menuEntry;
87        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 = menu->Entries.First; menuEntry;
121        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 = menu->Entries.First; menuEntry;
259        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 = menu->Entries.First, i=0; menuEntry;
292        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 = menu->Entries.First; menuEntry;
338        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 = menu->Entries.First; menuEntry; 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
470   /*
471    * Set up the initial menu position now:
472    */
473
474   menu->X = window->State.MouseX + glutGet ( GLUT_WINDOW_X ) ;
475   menu->Y = window->State.MouseY + glutGet ( GLUT_WINDOW_Y ) ;
476
477   fgSetWindow ( menu->Window ) ;
478   glutPositionWindow ( menu->X, menu->Y ) ;
479   glutReshapeWindow ( menu->Width, menu->Height ) ;
480   glutPopWindow () ;
481   glutShowWindow () ;
482   menu->Window->ActiveMenu = menu ;
483
484 /*  if( x > ( glutGet( GLUT_WINDOW_WIDTH ) - menu->Width ) )
485     menu->X = glutGet( GLUT_WINDOW_WIDTH ) - menu->Width;
486   if( y > ( glutGet( GLUT_WINDOW_HEIGHT ) - menu->Height) )
487     menu->Y = glutGet( GLUT_WINDOW_HEIGHT ) - menu->Height; */
488 }
489
490 /*
491  * Check whether an active menu absorbs a mouse click
492  */
493 GLboolean fgCheckActiveMenu ( SFG_Window *window, SFG_Menu *menu )
494 {
495   /*
496    * Near as I can tell, this is the active menu behaviour:
497    *  - Down-click any button outside the menu, menu active:  deactivate the menu
498    *  - Down-click any button inside the menu, menu active:  select the menu entry and deactivate the menu
499    *  - Up-click the menu button outside the menu, menu active:  nothing happens
500    *  - Up-click the menu button inside the menu, menu active:  select the menu entry and deactivate the menu
501    * Since menus can have submenus, we need to check this recursively.
502    */
503   return fghCheckMenuStatus ( window, menu ) ;
504 }
505
506 /*
507  * Function to check for menu entry selection on menu deactivation
508  */
509 void fgExecuteMenuCallback( SFG_Menu* menu )
510 {
511   SFG_MenuEntry *menuEntry;
512
513   /*
514    * First of all check any of the active sub menus...
515    */
516   for( menuEntry = menu->Entries.First; menuEntry; menuEntry = menuEntry->Node.Next)
517   {
518     /*
519      * Is this menu entry active?
520      */
521     if( menuEntry->IsActive == TRUE )
522     {
523       /*
524        * If there is not a sub menu, execute the menu callback and return...
525        */
526       if( menuEntry->SubMenu == NULL )
527       {
528         /*
529          * ...certainly given that there is one...
530          */
531         if( menu->Callback != NULL )
532           menu->Callback( menuEntry->ID );
533
534         return;
535       }
536
537       /*
538        * Otherwise recurse into the submenu.
539        */
540       fgExecuteMenuCallback( menuEntry->SubMenu );
541
542       /*
543        * There is little sense in dwelling the search on
544        */
545       return;
546     }
547   }
548 }
549
550 /*
551  * Deactivates a menu pointed by the function argument.
552  */
553 void fgDeactivateMenu( SFG_Window *window )
554 {
555   SFG_Window *current_window = fgStructure.Window ;
556
557     /*
558      * Check if there is an active menu attached to this window...
559      */
560     SFG_Menu* menu = window->ActiveMenu;
561     SFG_MenuEntry *menuEntry ;
562
563     /*
564      * Did we find an active window?
565      */
566     freeglut_return_if_fail( menu != NULL );
567
568     /*
569      * Hide the present menu's window
570      */
571     fgSetWindow ( menu->Window ) ;
572     glutHideWindow () ;
573
574     /*
575      * Forget about having that menu active anymore, now:
576      */
577     menu->Window->ActiveMenu = NULL ;
578     menu->ParentWindow->ActiveMenu = NULL ;
579     menu->IsActive = FALSE ;
580
581     /*
582      * Hide all submenu windows, and the root menu's window.
583      */
584     for ( menuEntry = menu->Entries.First; menuEntry;
585           menuEntry = menuEntry->Node.Next )
586     {
587       /*
588        * Is that an active submenu by any case?
589        */
590       if ( ( menuEntry->SubMenu != NULL ) && menuEntry->SubMenu->IsActive )
591         fgDeactivateSubMenu ( menuEntry ) ;
592     }
593
594     fgStructure.Window = current_window ;
595 }
596
597 /*
598  * Deactivates a menu pointed by the function argument.
599  */
600 void fgDeactivateSubMenu( SFG_MenuEntry *menuEntry )
601 {
602   SFG_Window *current_window = fgStructure.Window ;
603   SFG_MenuEntry *subMenuIter ;
604     /*
605      * Hide the present menu's window
606      */
607     fgSetWindow ( menuEntry->SubMenu->Window ) ;
608     glutHideWindow () ;
609
610     /*
611      * Forget about having that menu active anymore, now:
612      */
613     menuEntry->SubMenu->Window->ActiveMenu = NULL ;
614     menuEntry->SubMenu->IsActive = FALSE ;
615
616     /*
617      * Hide all submenu windows, and the root menu's window.
618      */
619     for ( subMenuIter = menuEntry->SubMenu->Entries.First; subMenuIter;
620           subMenuIter = subMenuIter->Node.Next )
621     {
622       /*
623        * Is that an active submenu by any case?
624        */
625       if ( ( subMenuIter->SubMenu != NULL ) && subMenuIter->SubMenu->IsActive )
626         fgDeactivateSubMenu ( subMenuIter ) ;
627     }
628
629     fgStructure.Window = current_window ;
630 }
631
632 /*
633  * Recalculates current menu's box size
634  */
635 void fghCalculateMenuBoxSize( void )
636 {
637   SFG_MenuEntry* menuEntry;
638   int width = 0, height = 0;
639
640   /*
641    * Make sure there is a current menu set
642    */
643   freeglut_assert_ready; freeglut_return_if_fail( fgStructure.Menu != NULL );
644
645   /*
646    * The menu's box size depends on the menu entries:
647    */
648   for( menuEntry = fgStructure.Menu->Entries.First; menuEntry;
649        menuEntry = menuEntry->Node.Next)
650   {
651     /*
652      * Update the menu entry's width value
653      */
654     menuEntry->Width = glutBitmapLength( FREEGLUT_MENU_FONT, menuEntry->Text );
655
656     /*
657      * If the entry is a submenu, then it needs to be wider to accomodate the arrow. JCJ 31 July 2003
658      */
659
660     if (menuEntry->SubMenu != NULL)
661        menuEntry->Width += glutBitmapLength( FREEGLUT_MENU_FONT, " " );
662
663     /*
664      * Check if it's the biggest we've found
665      */
666     if( menuEntry->Width > width )
667       width = menuEntry->Width;
668
669     height += FREEGLUT_MENU_HEIGHT;
670   }
671
672   /*
673    * Store the menu's box size now:
674    */
675   fgStructure.Menu->Height = height; 
676   fgStructure.Menu->Width  = width + 2 * FREEGLUT_MENU_BORDER ;
677 }
678
679
680 /* -- INTERFACE FUNCTIONS -------------------------------------------------- */
681
682 /*
683  * Creates a new menu object, adding it to the freeglut structure
684  */
685 int FGAPIENTRY glutCreateMenu( void (* callback)( int ) )
686 {
687     /*
688      * The menu object creation code resides in freeglut_structure.c
689      */
690     return( fgCreateMenu( callback )->ID );
691 }
692
693 /*
694  * Destroys a menu object, removing all references to it
695  */
696 void FGAPIENTRY glutDestroyMenu( int menuID )
697 {
698     SFG_Menu* menu = fgMenuByID( menuID );
699
700     freeglut_assert_ready; freeglut_return_if_fail( menu != NULL );
701
702     /*
703      * The menu object destruction code resides in freeglut_structure.c
704      */
705     fgDestroyMenu( menu );
706 }
707
708 /*
709  * Returns the ID number of the currently active menu
710  */
711 int FGAPIENTRY glutGetMenu( void )
712 {
713     freeglut_assert_ready;
714
715     /*
716      * Is there a current menu set?
717      */
718     if( fgStructure.Menu != NULL )
719     {
720         /*
721          * Yes, there is indeed...
722          */
723         return( fgStructure.Menu->ID );
724     }
725
726     /*
727      * No, there is no current menu at all
728      */
729     return( 0 );
730 }
731
732 /*
733  * Sets the current menu given its menu ID
734  */
735 void FGAPIENTRY glutSetMenu( int menuID )
736 {
737     SFG_Menu* menu = fgMenuByID( menuID );
738
739     freeglut_assert_ready; freeglut_return_if_fail( menu != NULL );
740
741     /*
742      * The current menu pointer is stored in fgStructure.Menu
743      */
744     fgStructure.Menu = menu;
745 }
746
747 /*
748  * Adds a menu entry to the bottom of the current menu
749  */
750 void FGAPIENTRY glutAddMenuEntry( const char* label, int value )
751 {
752     SFG_MenuEntry* menuEntry = calloc( sizeof(SFG_MenuEntry), 1 );
753
754     /*
755      * Make sure there is a current menu set
756      */
757     freeglut_assert_ready; freeglut_return_if_fail( fgStructure.Menu != NULL );
758
759     /*
760      * Fill in the appropriate values...
761      */
762     menuEntry->Text = strdup( label );
763     menuEntry->ID   = value;
764
765     /*
766      * Have the new menu entry attached to the current menu
767      */
768     fgListAppend( &fgStructure.Menu->Entries, &menuEntry->Node );
769
770     /*
771      * Update the menu's dimensions now
772      */
773     fghCalculateMenuBoxSize();
774 }
775
776 /*
777  * Add a sub menu to the bottom of the current menu
778  */
779 void FGAPIENTRY glutAddSubMenu( const char* label, int subMenuID )
780 {
781   SFG_MenuEntry* menuEntry = calloc( sizeof(SFG_MenuEntry), 1 );
782   SFG_Menu*      subMenu = fgMenuByID( subMenuID );
783
784   /*
785    * Make sure there is a current menu and the sub menu
786    * we want to attach actually exists...
787    */
788   freeglut_assert_ready; freeglut_return_if_fail( fgStructure.Menu != NULL );
789   freeglut_return_if_fail( subMenu != NULL );
790
791   /*
792    * Fill in the appropriate values
793    */
794   menuEntry->Text = strdup( label );
795   menuEntry->SubMenu = subMenu;
796   menuEntry->ID      = -1;
797
798   /*
799    * Make the submenu's parent window be the menu's parent window
800    */
801   fghSetSubmenuParentWindow ( fgStructure.Menu->ParentWindow, subMenu ) ;
802
803   /*
804    * Have the new menu entry attached to the current menu
805    */
806   fgListAppend( &fgStructure.Menu->Entries, &menuEntry->Node );
807
808   /*
809    * Update the menu's dimensions now
810    */
811   fghCalculateMenuBoxSize();
812 }
813
814 /*
815  * Changes the specified menu item in the current menu into a menu entry
816  */
817 void FGAPIENTRY glutChangeToMenuEntry( int item, const char* label, int value )
818 {
819     SFG_MenuEntry* menuEntry = NULL;
820
821     /*
822      * Make sure there is a current menu set...
823      */
824     freeglut_assert_ready; freeglut_return_if_fail( fgStructure.Menu != NULL );
825
826     /*
827      * Get n-th menu entry in the current menu, starting from one:
828      */
829     menuEntry = fghFindMenuEntry( fgStructure.Menu, item );
830
831     /*
832      * Make sure the menu entry exists
833      */
834     freeglut_return_if_fail( menuEntry != NULL );
835
836     /*
837      * We want it to become a normal menu entry, so:
838      */
839     if( menuEntry->Text != NULL )
840         free( menuEntry->Text );
841
842     menuEntry->Text = strdup( label );
843     menuEntry->ID      = value;
844     menuEntry->SubMenu = NULL;
845
846     /*
847      * Update the menu's dimensions now
848      */
849     fghCalculateMenuBoxSize();
850 }
851
852 /*
853  * Changes the specified menu item in the current menu into a sub-menu trigger.
854  */
855 void FGAPIENTRY glutChangeToSubMenu( int item, const char* label, int subMenuID )
856 {
857     SFG_Menu*      subMenu = fgMenuByID( subMenuID );
858     SFG_MenuEntry* menuEntry = NULL;
859
860     /*
861      * Make sure there is a current menu set and the sub menu exists...
862      */
863     freeglut_assert_ready; freeglut_return_if_fail( fgStructure.Menu != NULL );
864     freeglut_return_if_fail( subMenu != NULL );
865
866     /*
867      * Get n-th menu entry in the current menu, starting from one:
868      */
869     menuEntry = fghFindMenuEntry( fgStructure.Menu, item );
870
871     /*
872      * Make sure the menu entry exists
873      */
874     freeglut_return_if_fail( menuEntry != NULL );
875
876     /*
877      * We want it to become a sub menu entry, so:
878      */
879     if( menuEntry->Text != NULL )
880         free( menuEntry->Text );
881
882     menuEntry->Text = strdup( label );
883     menuEntry->SubMenu = subMenu;
884     menuEntry->ID      = -1;
885
886     /*
887      * Update the menu's dimensions now
888      */
889     fghCalculateMenuBoxSize();
890 }
891
892 /*
893  * Removes the specified menu item from the current menu
894  */
895 void FGAPIENTRY glutRemoveMenuItem( int item )
896 {
897     SFG_MenuEntry* menuEntry;
898
899     /*
900      * Make sure there is a current menu set
901      */
902     freeglut_assert_ready; freeglut_return_if_fail( fgStructure.Menu != NULL );
903
904     /*
905      * Get n-th menu entry in the current menu, starting from one:
906      */
907     menuEntry = fghFindMenuEntry( fgStructure.Menu, item );
908
909     /*
910      * Make sure the menu entry exists
911      */
912     freeglut_return_if_fail( menuEntry != NULL );
913
914     /*
915      * Removing a menu entry is quite simple...
916      */
917     fgListRemove( &fgStructure.Menu->Entries, &menuEntry->Node );
918
919     /*
920      * Free the entry label string, too
921      */
922     free( menuEntry->Text );
923
924     free( menuEntry );
925
926     /*
927      * Update the menu's dimensions now
928      */
929     fghCalculateMenuBoxSize();
930 }
931
932 /*
933  * Attaches a menu to the current window
934  */
935 void FGAPIENTRY glutAttachMenu( int button )
936 {
937     freeglut_assert_ready;
938
939     /*
940      * There must be a current window and a current menu set:
941      */
942     freeglut_return_if_fail( fgStructure.Window != NULL || fgStructure.Menu != NULL );
943
944     /*
945      * Make sure the button value is valid (0, 1 or 2, see freeglut.h)
946      */
947     freeglut_return_if_fail( button == GLUT_LEFT_BUTTON || button == GLUT_MIDDLE_BUTTON || button == GLUT_RIGHT_BUTTON );
948
949     /*
950      * It is safe now to attach the menu
951      */
952     fgStructure.Window->Menu[ button ] = fgStructure.Menu;
953
954     /*
955      * Make the parent window of the menu (and all submenus) the current window
956      */
957     fghSetSubmenuParentWindow ( fgStructure.Window, fgStructure.Menu ) ;
958 }
959
960 /*
961  * Detaches a menu from the current window
962  */
963 void FGAPIENTRY glutDetachMenu( int button )
964 {
965     freeglut_assert_ready;
966
967     /*
968      * There must be a current window set:
969      */
970     freeglut_return_if_fail( fgStructure.Window != NULL );
971
972     /*
973      * Make sure the button value is valid (0, 1 or 2, see freeglut.h)
974      */
975     freeglut_return_if_fail( button != 0 && button != 1 && button != 2 );
976
977     /*
978      * It is safe now to detach the menu
979      */
980     fgStructure.Window->Menu[ button ] = NULL;
981 }
982
983 /*
984  * A.Donev: Set and retrieve the menu's user data
985  */
986 void* FGAPIENTRY glutGetMenuData( void )
987 {
988    return(fgStructure.Menu->UserData);
989 }
990
991 void FGAPIENTRY glutSetMenuData(void* data)
992 {
993   fgStructure.Menu->UserData=data;
994 }
995
996 /*** END OF FILE ***/