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