0527c739da352d113a16c9cbe785665760148d4e
[freeglut] / freeglut-1.3 / 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       if( fghCheckMenuStatus( window, menuEntry->SubMenu ) == TRUE )
100         return( TRUE );
101     }
102   }
103
104   /*
105    * That much about our sub menus, let's get to checking the current menu:
106    */
107   x = window->State.MouseX - menu->X;
108   y = window->State.MouseY - menu->Y;
109
110   /*
111    * Mark all menu entries inactive...
112    */
113   for( menuEntry = menu->Entries.First; menuEntry;
114        menuEntry = menuEntry->Node.Next )
115   {
116     menuEntry->IsActive = FALSE;
117   }
118
119   menu->IsActive = FALSE;
120
121   /*
122    * Check if the mouse cursor is contained within the current menu box
123    */
124   if( x >= 0 && x < menu->Width && y >= 0 && y < menu->Height )
125   {
126     /*
127      * Calculation of the highlighted menu item is easy enough now:
128      */
129     int menuID = y / FREEGLUT_MENU_HEIGHT;
130
131     /*
132      * The mouse cursor is somewhere over our box, check it out.
133      */
134     menuEntry = fghFindMenuEntry( menu, menuID + 1 );
135     assert( menuEntry != NULL );
136
137     /*
138      * Mark the menu as active...
139      */
140     menuEntry->IsActive = TRUE;
141     menuEntry->Ordinal = menuID;
142
143     /*
144      * Don't forget about marking the current menu as active, too:
145      */
146     menu->IsActive = TRUE;
147
148     /*
149      * OKi, we have marked that entry as active, but it would be also
150      * nice to have its contents updated, in case it's a sub menu.
151      * Also, ignore the return value of the check function:
152      */
153     if( menuEntry->SubMenu != NULL )
154     {
155       /*
156        * Set up the initial menu position now...
157        */
158
159       menuEntry->SubMenu->X = menu->X + menu->Width ;
160       menuEntry->SubMenu->Y = menu->Y + menuEntry->Ordinal * FREEGLUT_MENU_HEIGHT ;
161
162       /*
163        * Make sure the submenu stays within the window
164        */
165       if ( menuEntry->SubMenu->X + menuEntry->SubMenu->Width > glutGet ( GLUT_WINDOW_WIDTH ) )
166       {
167         menuEntry->SubMenu->X = menu->X - menuEntry->SubMenu->Width ;
168         if ( menuEntry->SubMenu->X < 0 )
169           menuEntry->SubMenu->X = glutGet ( GLUT_WINDOW_WIDTH ) - menuEntry->SubMenu->Width ;
170       }
171
172       /*
173        * ...then check the submenu's state:
174        */
175       fghCheckMenuStatus( window, menuEntry->SubMenu );
176
177       /*
178        * Even if the submenu turned up inactive, activate it because its parent entry is active
179        */
180       menuEntry->SubMenu->IsActive = TRUE ;
181     }
182
183     /*
184      * Report back that we have caught the menu cursor
185      */
186     return( TRUE );
187   }
188
189   /*
190    * Looks like the menu cursor is somewhere else...
191    */
192   return( FALSE );
193 }
194
195 /*
196  * Displays a menu box and all of its submenus (if they are active)
197  */
198 static void fghDisplayMenuBox( SFG_Menu* menu )
199 {
200   SFG_MenuEntry *menuEntry;
201   int i;
202
203   /*
204    * Have the menu box drawn first. The +- values are
205    * here just to make it more nice-looking...
206    */
207   glColor4f( 0.0, 0.0, 0.0, 1.0 );
208   glBegin( GL_QUADS );
209     glVertex2f( menu->X              , menu->Y - 1                );
210     glVertex2f( menu->X + menu->Width, menu->Y - 1                );
211     glVertex2f( menu->X + menu->Width, menu->Y + 4 + menu->Height );
212     glVertex2f( menu->X              , menu->Y + 4 + menu->Height );
213   glEnd();
214
215   glColor4f( 0.3, 0.4, 0.5, 1.0 );
216   glBegin( GL_QUADS );
217     glVertex2f( menu->X - 2              , menu->Y + 1                );
218     glVertex2f( menu->X - 2 + menu->Width, menu->Y + 1                );
219     glVertex2f( menu->X - 2 + menu->Width, menu->Y + 2 + menu->Height );
220     glVertex2f( menu->X - 2              , menu->Y + 2 + menu->Height );
221   glEnd();
222
223   /*
224    * Check if any of the submenus is currently active...
225    */
226   for( menuEntry = menu->Entries.First; menuEntry;
227        menuEntry = menuEntry->Node.Next )
228   {
229     /*
230      * Has the menu been marked as active, maybe?
231      */
232     if( menuEntry->IsActive == TRUE )
233     {
234       /*
235        * That's truly right, and we need to have it highlighted.
236        * There is an assumption that mouse cursor didn't move
237        * since the last check of menu activity state:
238        */
239       int menuID = menuEntry->Ordinal;
240
241       /*
242        * So have the highlight drawn...
243        */
244       glColor4f( 0.2, 0.3, 0.4, 1.0 );
245       glBegin( GL_QUADS );
246         glVertex2f( menu->X - 2              , menu->Y + (menuID + 0)*FREEGLUT_MENU_HEIGHT + 1 );
247         glVertex2f( menu->X - 2 + menu->Width, menu->Y + (menuID + 0)*FREEGLUT_MENU_HEIGHT + 1 );
248         glVertex2f( menu->X - 2 + menu->Width, menu->Y + (menuID + 1)*FREEGLUT_MENU_HEIGHT + 2 );
249         glVertex2f( menu->X - 2              , menu->Y + (menuID + 1)*FREEGLUT_MENU_HEIGHT + 2 );
250       glEnd();
251     }
252   }
253
254   /*
255    * Print the menu entries now...
256    */
257   glColor4f( 1, 1, 1, 1 );
258
259   for( menuEntry = menu->Entries.First, i=0; menuEntry;
260        menuEntry = menuEntry->Node.Next, ++i )
261   {
262     /*
263      * Move the raster into position...
264      */
265     glRasterPos2i(
266         menu->X + FREEGLUT_MENU_BORDER,
267         menu->Y + (i + 1)*FREEGLUT_MENU_HEIGHT
268     );
269
270     /*
271      * Have the label drawn, character after character:
272      */
273     glutBitmapString( FREEGLUT_MENU_FONT, menuEntry->Text);
274
275     /*
276      * If it's a submenu, draw a right arrow
277      */
278     if ( menuEntry->SubMenu != NULL )
279     {
280       GLubyte arrow_char [] = { 0, 0, 32, 48, 56, 60, 62, 63, 62, 60, 56, 48, 32, 0, 0 } ;
281       int width = glutBitmapWidth ( FREEGLUT_MENU_FONT, ' ' ) ;
282
283       glPushClientAttrib( GL_CLIENT_PIXEL_STORE_BIT );
284
285       /*
286        * Set up the pixel unpacking ways
287        */
288       glPixelStorei( GL_UNPACK_SWAP_BYTES,  GL_FALSE );
289       glPixelStorei( GL_UNPACK_LSB_FIRST,   GL_FALSE );
290       glPixelStorei( GL_UNPACK_ROW_LENGTH,  0        );
291       glPixelStorei( GL_UNPACK_SKIP_ROWS,   0        );
292       glPixelStorei( GL_UNPACK_SKIP_PIXELS, 0        );
293       glPixelStorei( GL_UNPACK_ALIGNMENT,   1        );
294
295       glRasterPos2i ( menu->X + menu->Width - 2 - width,
296                       menu->Y + (i + 1)*FREEGLUT_MENU_HEIGHT ) ;
297       glBitmap ( width, FREEGLUT_MENU_HEIGHT, 0, 0, 0.0, 0.0, arrow_char ) ;
298       glPopClientAttrib();
299     }
300   }
301
302   /*
303    * Now we are ready to check if any of our children needs to be redrawn:
304    */
305   for( menuEntry = menu->Entries.First; menuEntry;
306        menuEntry = menuEntry->Node.Next )
307   {
308     /*
309      * Is that an active sub menu by any case?
310      */
311     if( menuEntry->SubMenu != NULL && menuEntry->IsActive == TRUE )
312     {
313       /*
314        * Yeah, indeed. Have it redrawn now:
315        */
316       fghDisplayMenuBox( menuEntry->SubMenu );
317     }
318   }
319 }
320
321 /*
322  * Displays the currently active menu for the current window
323  */
324 void fgDisplayMenu( void )
325 {
326     SFG_Window* window = fgStructure.Window;
327     SFG_Menu* menu = NULL;
328
329     /*
330      * Make sure there is a current window available
331      */
332     freeglut_assert_window;
333
334     /*
335      * Check if there is an active menu attached to this window...
336      */
337     menu = window->ActiveMenu;
338
339     /*
340      * Did we find an active window?
341      */
342     freeglut_return_if_fail( menu != NULL );
343     /*
344      * Prepare the OpenGL state to do the rendering first:
345      */
346     glPushAttrib( GL_DEPTH_BUFFER_BIT | GL_TEXTURE_BIT | GL_LIGHTING_BIT | GL_POLYGON_BIT );
347
348     glDisable( GL_DEPTH_TEST );
349     glDisable( GL_TEXTURE_2D );
350     glDisable( GL_LIGHTING   );
351     glDisable( GL_CULL_FACE  );
352
353     /*
354      * We'll use an orthogonal projection matrix to draw the menu:
355      */
356     glMatrixMode( GL_PROJECTION );
357     glPushMatrix();
358     glLoadIdentity();
359     glOrtho(
360          0, glutGet( GLUT_WINDOW_WIDTH  ),
361          glutGet( GLUT_WINDOW_HEIGHT ), 0,
362         -1, 1
363     );
364
365     /*
366      * Model-view matix gets reset to identity:
367      */
368     glMatrixMode( GL_MODELVIEW );
369     glPushMatrix();
370     glLoadIdentity();
371
372     /*
373      * First of all, have the exact menu status check:
374      */
375     fghCheckMenuStatus( window, menu );
376
377     /*
378      * The status has been updated and we're ready to have the menu drawn now:
379      */
380     fghDisplayMenuBox( menu );
381
382     /*
383      * Restore the old OpenGL settings now
384      */
385     glPopAttrib();
386
387     glMatrixMode( GL_PROJECTION );
388     glPopMatrix();
389     glMatrixMode( GL_MODELVIEW );
390     glPopMatrix();
391 }
392
393 /*
394  * Activates a menu pointed by the function argument
395  */
396 void fgActivateMenu( SFG_Window* window, int button )
397 {
398   int x, y;
399
400   /*
401    * We'll be referencing this menu a lot, so remember its address:
402    */
403   SFG_Menu* menu = window->Menu[ button ];
404
405   /*
406    * Mark the menu as active, so that it gets displayed:
407    */
408   window->ActiveMenu = menu;
409   menu->IsActive = TRUE ;
410
411   /*
412    * Grab the mouse cursor position respective to the current window
413    */
414   x = window->State.MouseX;
415   y = window->State.MouseY;
416
417   /*
418    * Set up the initial menu position now:
419    */
420   menu->X = x ;
421   menu->Y = y ;
422
423   fgSetWindow ( window ) ;
424
425   if( x > ( glutGet( GLUT_WINDOW_WIDTH ) - menu->Width ) )
426     menu->X = glutGet( GLUT_WINDOW_WIDTH ) - menu->Width;
427   if( y > ( glutGet( GLUT_WINDOW_HEIGHT ) - menu->Height) )
428     menu->Y = glutGet( GLUT_WINDOW_HEIGHT ) - menu->Height;
429 }
430
431 /*
432  * Check whether an active menu absorbs a mouse click
433  */
434 GLboolean fgCheckActiveMenu ( SFG_Window *window, SFG_Menu *menu )
435 {
436   /*
437    * Near as I can tell, this is the active menu behaviour:
438    *  - Down-click any button outside the menu, menu active:  deactivate the menu
439    *  - Down-click any button inside the menu, menu active:  select the menu entry and deactivate the menu
440    *  - Up-click the menu button outside the menu, menu active:  nothing happens
441    *  - Up-click the menu button inside the menu, menu active:  select the menu entry and deactivate the menu
442    * Since menus can have submenus, we need to check this recursively.
443    */
444   return fghCheckMenuStatus ( window, menu ) ;
445 }
446
447 /*
448  * Function to check for menu entry selection on menu deactivation
449  */
450 void fgExecuteMenuCallback( SFG_Menu* menu )
451 {
452   SFG_MenuEntry *menuEntry;
453
454   /*
455    * First of all check any of the active sub menus...
456    */
457   for( menuEntry = menu->Entries.First; menuEntry; menuEntry = menuEntry->Node.Next)
458   {
459     /*
460      * Is this menu entry active?
461      */
462     if( menuEntry->IsActive == TRUE )
463     {
464       /*
465        * If there is not a sub menu, execute the menu callback and return...
466        */
467       if( menuEntry->SubMenu == NULL )
468       {
469         /*
470          * ...certainly given that there is one...
471          */
472         if( menu->Callback != NULL )
473           menu->Callback( menuEntry->ID );
474
475         return;
476       }
477
478       /*
479        * Otherwise recurse into the submenu.
480        */
481       fgExecuteMenuCallback( menuEntry->SubMenu );
482
483       /*
484        * There is little sense in dwelling the search on
485        */
486       return;
487     }
488   }
489 }
490
491 /*
492  * Deactivates a menu pointed by the function argument.
493  */
494 void fgDeactivateMenu( SFG_Window *window )
495 {
496     /*
497      * Check if there is an active menu attached to this window...
498      */
499     SFG_Menu* menu = window->ActiveMenu;
500
501     /*
502      * Did we find an active window?
503      */
504     freeglut_return_if_fail( menu != NULL );
505
506     /*
507      * Forget about having that menu active anymore, now:
508      */
509     window->ActiveMenu = NULL;
510     menu->IsActive = FALSE ;
511 }
512
513 /*
514  * Recalculates current menu's box size
515  */
516 void fghCalculateMenuBoxSize( void )
517 {
518   SFG_MenuEntry* menuEntry;
519   int width = 0, height = 0;
520
521   /*
522    * Make sure there is a current menu set
523    */
524   freeglut_assert_ready; freeglut_return_if_fail( fgStructure.Menu != NULL );
525
526   /*
527    * The menu's box size depends on the menu entries:
528    */
529   for( menuEntry = fgStructure.Menu->Entries.First; menuEntry;
530        menuEntry = menuEntry->Node.Next)
531   {
532     /*
533      * Update the menu entry's width value
534      */
535     menuEntry->Width = glutBitmapLength( FREEGLUT_MENU_FONT, menuEntry->Text );
536
537     /*
538      * Check if it's the biggest we've found
539      */
540     if( menuEntry->Width > width )
541       width = menuEntry->Width;
542
543     height += FREEGLUT_MENU_HEIGHT;
544   }
545
546   /*
547    * Store the menu's box size now:
548    */
549   fgStructure.Menu->Height = height; 
550   fgStructure.Menu->Width  = width + 2 * FREEGLUT_MENU_BORDER ;
551 }
552
553
554 /* -- INTERFACE FUNCTIONS -------------------------------------------------- */
555
556 /*
557  * Creates a new menu object, adding it to the freeglut structure
558  */
559 int FGAPIENTRY glutCreateMenu( void (* callback)( int ) )
560 {
561     /*
562      * The menu object creation code resides in freeglut_structure.c
563      */
564     return( fgCreateMenu( callback )->ID );
565 }
566
567 /*
568  * Destroys a menu object, removing all references to it
569  */
570 void FGAPIENTRY glutDestroyMenu( int menuID )
571 {
572     SFG_Menu* menu = fgMenuByID( menuID );
573
574     freeglut_assert_ready; freeglut_return_if_fail( menu != NULL );
575
576     /*
577      * The menu object destruction code resides in freeglut_structure.c
578      */
579     fgDestroyMenu( menu );
580 }
581
582 /*
583  * Returns the ID number of the currently active menu
584  */
585 int FGAPIENTRY glutGetMenu( void )
586 {
587     freeglut_assert_ready;
588
589     /*
590      * Is there a current menu set?
591      */
592     if( fgStructure.Menu != NULL )
593     {
594         /*
595          * Yes, there is indeed...
596          */
597         return( fgStructure.Menu->ID );
598     }
599
600     /*
601      * No, there is no current menu at all
602      */
603     return( 0 );
604 }
605
606 /*
607  * Sets the current menu given its menu ID
608  */
609 void FGAPIENTRY glutSetMenu( int menuID )
610 {
611     SFG_Menu* menu = fgMenuByID( menuID );
612
613     freeglut_assert_ready; freeglut_return_if_fail( menu != NULL );
614
615     /*
616      * The current menu pointer is stored in fgStructure.Menu
617      */
618     fgStructure.Menu = menu;
619 }
620
621 /*
622  * Adds a menu entry to the bottom of the current menu
623  */
624 void FGAPIENTRY glutAddMenuEntry( const char* label, int value )
625 {
626     SFG_MenuEntry* menuEntry = calloc( sizeof(SFG_MenuEntry), 1 );
627
628     /*
629      * Make sure there is a current menu set
630      */
631     freeglut_assert_ready; freeglut_return_if_fail( fgStructure.Menu != NULL );
632
633     /*
634      * Fill in the appropriate values...
635      */
636     menuEntry->Text = strdup( label );
637     menuEntry->ID   = value;
638
639     /*
640      * Have the new menu entry attached to the current menu
641      */
642     fgListAppend( &fgStructure.Menu->Entries, &menuEntry->Node );
643
644     /*
645      * Update the menu's dimensions now
646      */
647     fghCalculateMenuBoxSize();
648 }
649
650 /*
651  * Add a sub menu to the bottom of the current menu
652  */
653 void FGAPIENTRY glutAddSubMenu( const char* label, int subMenuID )
654 {
655   SFG_MenuEntry* menuEntry = calloc( sizeof(SFG_MenuEntry), 1 );
656   SFG_Menu*      subMenu = fgMenuByID( subMenuID );
657
658   /*
659    * Make sure there is a current menu and the sub menu
660    * we want to attach actually exists...
661    */
662   freeglut_assert_ready; freeglut_return_if_fail( fgStructure.Menu != NULL );
663   freeglut_return_if_fail( subMenu != NULL );
664
665   /*
666    * Fill in the appropriate values
667    */
668   menuEntry->Text = strdup( label );
669   menuEntry->SubMenu = subMenu;
670   menuEntry->ID      = -1;
671
672   /*
673    * Have the new menu entry attached to the current menu
674    */
675   fgListAppend( &fgStructure.Menu->Entries, &menuEntry->Node );
676
677   /*
678    * Update the menu's dimensions now
679    */
680   fghCalculateMenuBoxSize();
681 }
682
683 /*
684  * Changes the specified menu item in the current menu into a menu entry
685  */
686 void FGAPIENTRY glutChangeToMenuEntry( int item, const char* label, int value )
687 {
688     SFG_MenuEntry* menuEntry = NULL;
689
690     /*
691      * Make sure there is a current menu set...
692      */
693     freeglut_assert_ready; freeglut_return_if_fail( fgStructure.Menu != NULL );
694
695     /*
696      * Get n-th menu entry in the current menu, starting from one:
697      */
698     menuEntry = fghFindMenuEntry( fgStructure.Menu, item );
699
700     /*
701      * Make sure the menu entry exists
702      */
703     freeglut_return_if_fail( menuEntry != NULL );
704
705     /*
706      * We want it to become a normal menu entry, so:
707      */
708     if( menuEntry->Text != NULL )
709         free( menuEntry->Text );
710
711     menuEntry->Text = strdup( label );
712     menuEntry->ID      = value;
713     menuEntry->SubMenu = NULL;
714
715     /*
716      * Update the menu's dimensions now
717      */
718     fghCalculateMenuBoxSize();
719 }
720
721 /*
722  * Changes the specified menu item in the current menu into a sub-menu trigger.
723  */
724 void FGAPIENTRY glutChangeToSubMenu( int item, const char* label, int subMenuID )
725 {
726     SFG_Menu*      subMenu = fgMenuByID( subMenuID );
727     SFG_MenuEntry* menuEntry = NULL;
728
729     /*
730      * Make sure there is a current menu set and the sub menu exists...
731      */
732     freeglut_assert_ready; freeglut_return_if_fail( fgStructure.Menu != NULL );
733     freeglut_return_if_fail( subMenu != NULL );
734
735     /*
736      * Get n-th menu entry in the current menu, starting from one:
737      */
738     menuEntry = fghFindMenuEntry( fgStructure.Menu, item );
739
740     /*
741      * Make sure the menu entry exists
742      */
743     freeglut_return_if_fail( menuEntry != NULL );
744
745     /*
746      * We want it to become a sub menu entry, so:
747      */
748     if( menuEntry->Text != NULL )
749         free( menuEntry->Text );
750
751     menuEntry->Text = strdup( label );
752     menuEntry->SubMenu = subMenu;
753     menuEntry->ID      = -1;
754
755     /*
756      * Update the menu's dimensions now
757      */
758     fghCalculateMenuBoxSize();
759 }
760
761 /*
762  * Removes the specified menu item from the current menu
763  */
764 void FGAPIENTRY glutRemoveMenuItem( int item )
765 {
766     SFG_MenuEntry* menuEntry;
767
768     /*
769      * Make sure there is a current menu set
770      */
771     freeglut_assert_ready; freeglut_return_if_fail( fgStructure.Menu != NULL );
772
773     /*
774      * Get n-th menu entry in the current menu, starting from one:
775      */
776     menuEntry = fghFindMenuEntry( fgStructure.Menu, item );
777
778     /*
779      * Make sure the menu entry exists
780      */
781     freeglut_return_if_fail( menuEntry != NULL );
782
783     /*
784      * Removing a menu entry is quite simple...
785      */
786     fgListRemove( &fgStructure.Menu->Entries, &menuEntry->Node );
787
788     /*
789      * Free the entry label string, too
790      */
791     free( menuEntry->Text );
792
793     free( menuEntry );
794
795     /*
796      * Update the menu's dimensions now
797      */
798     fghCalculateMenuBoxSize();
799 }
800
801 /*
802  * Attaches a menu to the current window
803  */
804 void FGAPIENTRY glutAttachMenu( int button )
805 {
806     freeglut_assert_ready;
807
808     /*
809      * There must be a current window and a current menu set:
810      */
811     freeglut_return_if_fail( fgStructure.Window != NULL || fgStructure.Menu != NULL );
812
813     /*
814      * Make sure the button value is valid (0, 1 or 2, see freeglut.h)
815      */
816     freeglut_return_if_fail( button == GLUT_LEFT_BUTTON || button == GLUT_MIDDLE_BUTTON || button == GLUT_RIGHT_BUTTON );
817
818     /*
819      * It is safe now to attach the menu
820      */
821     fgStructure.Window->Menu[ button ] = fgStructure.Menu;
822 }
823
824 /*
825  * Detaches a menu from the current window
826  */
827 void FGAPIENTRY glutDetachMenu( int button )
828 {
829     freeglut_assert_ready;
830
831     /*
832      * There must be a current window set:
833      */
834     freeglut_return_if_fail( fgStructure.Window != NULL );
835
836     /*
837      * Make sure the button value is valid (0, 1 or 2, see freeglut.h)
838      */
839     freeglut_return_if_fail( button != 0 && button != 1 && button != 2 );
840
841     /*
842      * It is safe now to detach the menu
843      */
844     fgStructure.Window->Menu[ button ] = NULL;
845 }
846
847 /*** END OF FILE ***/