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