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