Several updates from John and myself.
[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_FONT    GLUT_BITMAP_HELVETICA_18*/
52 #define FREEGLUT_MENU_HEIGHT  (glutBitmapHeight(FREEGLUT_MENU_FONT) + FREEGLUT_MENU_BORDER)
53 #define  FREEGLUT_MENU_BORDER   2
54
55
56 /*
57  * These variables should be moved into the freeglut global state, but for now,
58  * we'll put them here.  They are for rendering the freeglut menu items.
59  * The choices are fore- and background, with and without h for Highlighting.
60  * Old GLUT appeared to be system-dependant for its colors (sigh) so we are
61  * too.  These variables should be stuffed into global state and initialized
62  * via the glutInit*() system.
63  */
64 static float menu_pen_fore  [4] = {0.0f,  0.0f,  0.0f,  1.0f};
65 static float menu_pen_back  [4] = {0.85f, 0.85f, 0.85f, 1.0f};
66
67 #if TARGET_HOST_WIN32
68 static float menu_pen_hfore [4] = {1.0f,  1.0f,  1.0f,  1.0f};
69 static float menu_pen_hback [4] = {0.15f, 0.15f, 0.45f, 1.0f};
70 #else
71 static float menu_pen_hfore [4] = {0.0f,  0.0f,  0.0f,  1.0f};
72 static float menu_pen_hback [4] = {1.0f,  1.0f,  1.0f,  1.0f};
73 #endif
74
75
76 /* -- PRIVATE FUNCTIONS ---------------------------------------------------- */
77
78 /*
79  * Private static function to find a menu entry by index
80  */
81 static SFG_MenuEntry *fghFindMenuEntry( SFG_Menu* menu, int index )
82 {
83     SFG_MenuEntry *entry;
84     int i = 1;
85
86     for( entry = (SFG_MenuEntry *)menu->Entries.First; entry; entry = (SFG_MenuEntry *)entry->Node.Next)
87     {
88         if (i == index)
89             break;
90         ++i;
91     }
92
93     return entry;
94 }
95
96 /*
97  * Private static function to check for the current menu/sub menu activity state
98  */
99 static GLboolean fghCheckMenuStatus( SFG_Window* window, SFG_Menu* menu )
100 {
101   SFG_MenuEntry* menuEntry;
102   int x, y;
103
104   /*
105    * First of all check any of the active sub menus...
106    */
107   for( menuEntry = (SFG_MenuEntry *)menu->Entries.First; menuEntry;
108        menuEntry = (SFG_MenuEntry *)menuEntry->Node.Next )
109   {
110     /*
111      * Is that an active sub menu by any case?
112      */
113     if( menuEntry->SubMenu != NULL && menuEntry->IsActive == TRUE )
114     {
115       /*
116        * OK, have the sub-menu checked, too. If it returns TRUE, it will mean
117        * that it caught the mouse cursor and we do not need to regenerate
118        * the activity list, and so our parents do...
119        */
120       GLboolean return_status = fghCheckMenuStatus( window, menuEntry->SubMenu ) ;
121
122       /*
123        * Reactivate the submenu as the checkMenuStatus may have turned it off if the mouse
124        * is in its parent menu entry.
125        */
126       menuEntry->SubMenu->IsActive = TRUE ;
127       if ( return_status == TRUE )
128         return( TRUE );
129     }
130   }
131
132   /*
133    * That much about our sub menus, let's get to checking the current menu:
134    */
135   x = window->State.MouseX;
136   y = window->State.MouseY;
137
138   /*
139    * Mark all menu entries inactive...
140    */
141   for( menuEntry = (SFG_MenuEntry *)menu->Entries.First; menuEntry;
142        menuEntry = (SFG_MenuEntry *)menuEntry->Node.Next )
143   {
144     menuEntry->IsActive = FALSE;
145   }
146
147
148   menu->IsActive = FALSE;
149
150   /*
151    * Check if the mouse cursor is contained within the current menu box
152    */
153   if ( ( x >= FREEGLUT_MENU_BORDER ) && ( x < menu->Width  - FREEGLUT_MENU_BORDER ) &&
154        ( y >= FREEGLUT_MENU_BORDER ) && ( y < menu->Height - FREEGLUT_MENU_BORDER ) &&
155        ( window == menu->Window ) )
156   {
157     /*
158      * Calculation of the highlighted menu item is easy enough now:
159      */
160     int menuID = ( y - FREEGLUT_MENU_BORDER ) / FREEGLUT_MENU_HEIGHT ;
161
162     /*
163      * The mouse cursor is somewhere over our box, check it out.
164      */
165     menuEntry = fghFindMenuEntry( menu, menuID + 1 );
166     assert( menuEntry != NULL );
167
168     /*
169      * Mark the menu as active...
170      */
171     menuEntry->IsActive = TRUE;
172     menuEntry->Ordinal = menuID;
173
174     /*
175      * If this is not the same as the last active menu entry, deactivate the previous entry.
176      * Specifically, if the previous active entry was a submenu then deactivate it.
177      */
178     if ( menu->ActiveEntry && ( menuEntry != menu->ActiveEntry ) )
179     {
180       if ( menu->ActiveEntry->SubMenu != NULL )
181         fgDeactivateSubMenu ( menu->ActiveEntry ) ;
182     }
183
184     menu->ActiveEntry = menuEntry ;
185
186     /*
187      * Don't forget about marking the current menu as active, too:
188      */
189
190     menu->IsActive = TRUE;
191
192     /*
193      * OKi, we have marked that entry as active, but it would be also
194      * nice to have its contents updated, in case it's a sub menu.
195      * Also, ignore the return value of the check function:
196      */
197     if( menuEntry->SubMenu != NULL )
198     {
199       if ( ! menuEntry->SubMenu->IsActive )
200       {
201         SFG_Window *current_window = fgStructure.Window ;
202
203         /*
204          * Set up the initial menu position now...
205          */
206
207         /*
208          * Mark the menu as active, so that it gets displayed:
209          */
210         menuEntry->SubMenu->IsActive = TRUE ;
211
212         /*
213          * Set up the initial submenu position now:
214          */
215         menuEntry->SubMenu->X = menu->X + menu->Width ;
216         menuEntry->SubMenu->Y = menu->Y + menuEntry->Ordinal * FREEGLUT_MENU_HEIGHT ;
217
218         fgSetWindow ( menuEntry->SubMenu->Window ) ;
219         glutPositionWindow ( menuEntry->SubMenu->X, menuEntry->SubMenu->Y ) ;
220         glutReshapeWindow ( menuEntry->SubMenu->Width, menuEntry->SubMenu->Height ) ;
221         glutPopWindow () ;
222         glutShowWindow () ;
223         menuEntry->SubMenu->Window->ActiveMenu = menuEntry->SubMenu ;
224         fgSetWindow ( current_window ) ;
225       }
226
227       /*
228        * ...then check the submenu's state:
229        */
230       fghCheckMenuStatus( window, menuEntry->SubMenu );
231
232       /*
233        * Even if the submenu turned up inactive, activate it because its parent entry is active
234        */
235       menuEntry->SubMenu->IsActive = TRUE ;
236     }
237
238     /*
239      * Report back that we have caught the menu cursor
240      */
241     return( TRUE );
242   }
243
244   /*
245    * Looks like the menu cursor is somewhere else...
246    */
247   return( FALSE );
248 }
249
250 /*
251  * Displays a menu box and all of its submenus (if they are active)
252  */
253 static void fghDisplayMenuBox( SFG_Menu* menu )
254 {
255   SFG_MenuEntry *menuEntry;
256   int i;
257   int border = FREEGLUT_MENU_BORDER ;
258
259   /*
260    * Have the menu box drawn first. The +- values are
261    * here just to make it more nice-looking...
262    */
263   glColor4f( 1.0f, 1.0f, 1.0f, 1.0f ); /* a non-black dark version of the below. */
264   glBegin( GL_QUAD_STRIP );
265     glVertex2i( menu->Width       , 0                  );
266     glVertex2i( menu->Width-border,              border);
267     glVertex2i( 0                 , 0                  );
268     glVertex2i(             border,              border);
269     glVertex2i( 0                 , menu->Height       );
270     glVertex2i(             border, menu->Height-border);
271   glEnd();
272
273   glColor4f( 0.5f, 0.5f, 0.5f, 1.0f ); /* a non-black dark version of the below. */
274   glBegin( GL_QUAD_STRIP );
275     glVertex2i( 0                 , menu->Height       );
276     glVertex2i(             border, menu->Height-border);
277     glVertex2i( menu->Width       , menu->Height       );
278     glVertex2i( menu->Width-border, menu->Height-border);
279     glVertex2i( menu->Width       , 0                  );
280     glVertex2i( menu->Width-border,              border);
281   glEnd();
282
283   glColor4fv ( menu_pen_back ) ;
284   glBegin( GL_QUADS );
285     glVertex2i(             border,              border);
286     glVertex2i( menu->Width-border,              border);
287     glVertex2i( menu->Width-border, menu->Height-border);
288     glVertex2i(             border, menu->Height-border);
289   glEnd();
290
291   /*
292    * Check if any of the submenus is currently active...
293    */
294   for( menuEntry = (SFG_MenuEntry *)menu->Entries.First; menuEntry;
295        menuEntry = (SFG_MenuEntry *)menuEntry->Node.Next )
296   {
297     /*
298      * Has the menu been marked as active, maybe?
299      */
300     if( menuEntry->IsActive == TRUE )
301     {
302       /*
303        * That's truly right, and we need to have it highlighted.
304        * There is an assumption that mouse cursor didn't move
305        * since the last check of menu activity state:
306        */
307       int menuID = menuEntry->Ordinal;
308
309       /*
310        * So have the highlight drawn...
311        */
312       glColor4fv ( menu_pen_hback ) ;
313       glBegin( GL_QUADS );
314         glVertex2i(             FREEGLUT_MENU_BORDER, (menuID + 0)*FREEGLUT_MENU_HEIGHT + FREEGLUT_MENU_BORDER );
315         glVertex2i( menu->Width-FREEGLUT_MENU_BORDER, (menuID + 0)*FREEGLUT_MENU_HEIGHT + FREEGLUT_MENU_BORDER );
316         glVertex2i( menu->Width-FREEGLUT_MENU_BORDER, (menuID + 1)*FREEGLUT_MENU_HEIGHT + FREEGLUT_MENU_BORDER );
317         glVertex2i(             FREEGLUT_MENU_BORDER, (menuID + 1)*FREEGLUT_MENU_HEIGHT + FREEGLUT_MENU_BORDER );
318       glEnd();
319     }
320   }
321
322   /*
323    * Print the menu entries now...
324    */
325
326   glColor4fv ( menu_pen_fore );
327
328   for( menuEntry = (SFG_MenuEntry *)menu->Entries.First, i=0; menuEntry;
329        menuEntry = (SFG_MenuEntry *)menuEntry->Node.Next, ++i )
330   {
331     /*
332      * If the menu entry is active, set the color to white
333      */
334     if ( menuEntry->IsActive )
335       glColor4fv ( menu_pen_hfore ) ;
336
337     /*
338      * Move the raster into position...
339      */
340     glRasterPos2i(
341         2 * FREEGLUT_MENU_BORDER,
342         (i + 1)*FREEGLUT_MENU_HEIGHT-(int)(FREEGLUT_MENU_HEIGHT*0.3 - FREEGLUT_MENU_BORDER ) /* Try to center the text - JCJ 31 July 2003*/
343     );
344
345     /*
346      * Have the label drawn, character after character:
347      */
348     glutBitmapString( FREEGLUT_MENU_FONT, menuEntry->Text);
349
350     /*
351      * If it's a submenu, draw a right arrow
352      */
353     if ( menuEntry->SubMenu != NULL )
354     {
355       int width = glutBitmapWidth ( FREEGLUT_MENU_FONT, '_' ) ;
356       int x_base = menu->Width - 2 - width;
357       int y_base = i*FREEGLUT_MENU_HEIGHT + FREEGLUT_MENU_BORDER;
358       glBegin( GL_TRIANGLES );
359       glVertex2i( x_base, y_base + 2*FREEGLUT_MENU_BORDER);
360       glVertex2i( menu->Width - 2, y_base + (FREEGLUT_MENU_HEIGHT + FREEGLUT_MENU_BORDER) / 2 );
361       glVertex2i( x_base, y_base + FREEGLUT_MENU_HEIGHT - FREEGLUT_MENU_BORDER);
362       glEnd( );
363     }
364
365     /*
366      * If the menu entry is active, reset the color
367      */
368     if ( menuEntry->IsActive )
369       glColor4fv ( menu_pen_fore ) ;
370   }
371
372   /*
373    * Now we are ready to check if any of our children needs to be redrawn:
374    */
375   for( menuEntry = (SFG_MenuEntry *)menu->Entries.First; menuEntry;
376        menuEntry = (SFG_MenuEntry *)menuEntry->Node.Next )
377   {
378     /*
379      * Is that an active sub menu by any case?
380      */
381     if( menuEntry->SubMenu != NULL && menuEntry->IsActive == TRUE )
382     {
383       /*
384        * Yeah, indeed. Have it redrawn now:
385        */
386       fgSetWindow ( menuEntry->SubMenu->Window ) ;
387       fghDisplayMenuBox( menuEntry->SubMenu );
388       fgSetWindow ( menu->Window ) ;
389     }
390   }
391 }
392
393 /*
394  * Private static function to set the parent window of a submenu and all of its submenus
395  */
396 static void fghSetSubmenuParentWindow ( SFG_Window *window, SFG_Menu *menu )
397 {
398   SFG_MenuEntry *menuEntry ;
399
400   menu->ParentWindow = window ;
401
402   for ( menuEntry = (SFG_MenuEntry *)menu->Entries.First; menuEntry; menuEntry = (SFG_MenuEntry *)menuEntry->Node.Next )
403   {
404     if ( menuEntry->SubMenu != NULL )
405       fghSetSubmenuParentWindow ( window, menuEntry->SubMenu ) ;
406   }
407 }
408
409
410 /*
411  * Displays the currently active menu for the current window
412  */
413 void fgDisplayMenu( void )
414 {
415     SFG_Window* window = fgStructure.Window;
416     SFG_Menu* menu = NULL;
417
418     /*
419      * Make sure there is a current window available
420      */
421     freeglut_assert_window;
422
423     /*
424      * Check if there is an active menu attached to this window...
425      */
426     menu = window->ActiveMenu;
427
428     /*
429      * Did we find an active menu?
430      */
431     freeglut_return_if_fail( menu != NULL );
432
433     fgSetWindow ( menu->Window ) ;
434
435     /*
436      * Prepare the OpenGL state to do the rendering first:
437      */
438     glPushAttrib( GL_DEPTH_BUFFER_BIT | GL_TEXTURE_BIT | GL_LIGHTING_BIT | GL_POLYGON_BIT );
439
440     glDisable( GL_DEPTH_TEST );
441     glDisable( GL_TEXTURE_2D );
442     glDisable( GL_LIGHTING   );
443     glDisable( GL_CULL_FACE  );
444
445     /*
446      * We'll use an orthogonal projection matrix to draw the menu:
447      */
448     glMatrixMode( GL_PROJECTION );
449     glPushMatrix();
450     glLoadIdentity();
451     glOrtho(
452          0, glutGet( GLUT_WINDOW_WIDTH  ),
453          glutGet( GLUT_WINDOW_HEIGHT ), 0,
454         -1, 1
455     );
456
457     /*
458      * Model-view matix gets reset to identity:
459      */
460     glMatrixMode( GL_MODELVIEW );
461     glPushMatrix();
462     glLoadIdentity();
463
464     /*
465      * First of all, have the exact menu status check:
466      */
467     fghCheckMenuStatus( window, menu );
468
469     /*
470      * The status has been updated and we're ready to have the menu drawn now:
471      */
472     fghDisplayMenuBox( menu );
473
474     /*
475      * Restore the old OpenGL settings now
476      */
477     glPopAttrib();
478
479     glMatrixMode( GL_PROJECTION );
480     glPopMatrix();
481     glMatrixMode( GL_MODELVIEW );
482     glPopMatrix();
483
484     glutSwapBuffers () ;
485
486     /*
487      * Restore the current window
488      */
489     fgSetWindow ( window ) ;
490 }
491
492 /*
493  * Activates a menu pointed by the function argument
494  */
495 void fgActivateMenu( SFG_Window* window, int button )
496 {
497   /*
498    * We'll be referencing this menu a lot, so remember its address:
499    */
500   SFG_Menu* menu = window->Menu[ button ];
501
502   /*
503    * Mark the menu as active, so that it gets displayed:
504    */
505   window->ActiveMenu = menu;
506   menu->IsActive = TRUE ;
507   fgState.ActiveMenus ++ ;
508
509   /*
510    * Set up the initial menu position now:
511    */
512
513   menu->X = window->State.MouseX + glutGet ( GLUT_WINDOW_X ) ;
514   menu->Y = window->State.MouseY + glutGet ( GLUT_WINDOW_Y ) ;
515
516   fgSetWindow ( menu->Window ) ;
517   glutPositionWindow ( menu->X, menu->Y ) ;
518   glutReshapeWindow ( menu->Width, menu->Height ) ;
519   glutPopWindow () ;
520   glutShowWindow () ;
521   menu->Window->ActiveMenu = menu ;
522
523 /*  if( x > ( glutGet( GLUT_WINDOW_WIDTH ) - menu->Width ) )
524     menu->X = glutGet( GLUT_WINDOW_WIDTH ) - menu->Width;
525   if( y > ( glutGet( GLUT_WINDOW_HEIGHT ) - menu->Height) )
526     menu->Y = glutGet( GLUT_WINDOW_HEIGHT ) - menu->Height; */
527 }
528
529 /*
530  * Check whether an active menu absorbs a mouse click
531  */
532 GLboolean fgCheckActiveMenu ( SFG_Window *window, SFG_Menu *menu )
533 {
534   /*
535    * Near as I can tell, this is the active menu behaviour:
536    *  - Down-click any button outside the menu, menu active:  deactivate the menu
537    *  - Down-click any button inside the menu, menu active:  select the menu entry and deactivate the menu
538    *  - Up-click the menu button outside the menu, menu active:  nothing happens
539    *  - Up-click the menu button inside the menu, menu active:  select the menu entry and deactivate the menu
540    * Since menus can have submenus, we need to check this recursively.
541    */
542   return fghCheckMenuStatus ( window, menu ) ;
543 }
544
545 /*
546  * Function to check for menu entry selection on menu deactivation
547  */
548 void fgExecuteMenuCallback( SFG_Menu* menu )
549 {
550   SFG_MenuEntry *menuEntry;
551
552   /*
553    * First of all check any of the active sub menus...
554    */
555   for( menuEntry = (SFG_MenuEntry *)menu->Entries.First; menuEntry; menuEntry = (SFG_MenuEntry *)menuEntry->Node.Next)
556   {
557     /*
558      * Is this menu entry active?
559      */
560     if( menuEntry->IsActive == TRUE )
561     {
562       /*
563        * If there is not a sub menu, execute the menu callback and return...
564        */
565       if( menuEntry->SubMenu == NULL )
566       {
567         /*
568          * ...certainly given that there is one...
569          */
570         if( menu->Callback != NULL )
571           menu->Callback( menuEntry->ID );
572
573         return;
574       }
575
576       /*
577        * Otherwise recurse into the submenu.
578        */
579       fgExecuteMenuCallback( menuEntry->SubMenu );
580
581       /*
582        * There is little sense in dwelling the search on
583        */
584       return;
585     }
586   }
587 }
588
589 /*
590  * Deactivates a menu pointed by the function argument.
591  */
592 void fgDeactivateMenu( SFG_Window *window )
593 {
594   SFG_Window *current_window = fgStructure.Window ;
595
596     /*
597      * Check if there is an active menu attached to this window...
598      */
599     SFG_Menu* menu = window->ActiveMenu;
600     SFG_MenuEntry *menuEntry ;
601
602     /*
603      * Did we find an active window?
604      */
605     freeglut_return_if_fail( menu != NULL );
606
607     /*
608      * Hide the present menu's window
609      */
610     fgSetWindow ( menu->Window ) ;
611     glutHideWindow () ;
612
613     /*
614      * Forget about having that menu active anymore, now:
615      */
616     menu->Window->ActiveMenu = NULL ;
617     menu->ParentWindow->ActiveMenu = NULL ;
618     menu->IsActive = FALSE ;
619
620     fgState.ActiveMenus -- ;
621
622     /*
623      * Hide all submenu windows, and the root menu's window.
624      */
625     for ( menuEntry = (SFG_MenuEntry *)menu->Entries.First; menuEntry;
626           menuEntry = (SFG_MenuEntry *)menuEntry->Node.Next )
627     {
628       /*
629        * Is that an active submenu by any case?
630        */
631       if ( ( menuEntry->SubMenu != NULL ) && menuEntry->SubMenu->IsActive )
632         fgDeactivateSubMenu ( menuEntry ) ;
633     }
634
635     fgSetWindow ( current_window ) ;
636 }
637
638 /*
639  * Deactivates a menu pointed by the function argument.
640  */
641 void fgDeactivateSubMenu( SFG_MenuEntry *menuEntry )
642 {
643   SFG_Window *current_window = fgStructure.Window ;
644   SFG_MenuEntry *subMenuIter ;
645     /*
646      * Hide the present menu's window
647      */
648     fgSetWindow ( menuEntry->SubMenu->Window ) ;
649     glutHideWindow () ;
650
651     /*
652      * Forget about having that menu active anymore, now:
653      */
654     menuEntry->SubMenu->Window->ActiveMenu = NULL ;
655     menuEntry->SubMenu->IsActive = FALSE ;
656
657     /*
658      * Hide all submenu windows, and the root menu's window.
659      */
660     for ( subMenuIter = (SFG_MenuEntry *)menuEntry->SubMenu->Entries.First; subMenuIter;
661           subMenuIter = (SFG_MenuEntry *)subMenuIter->Node.Next )
662     {
663       /*
664        * Is that an active submenu by any case?
665        */
666       if ( ( subMenuIter->SubMenu != NULL ) && subMenuIter->SubMenu->IsActive )
667         fgDeactivateSubMenu ( subMenuIter ) ;
668     }
669
670     fgSetWindow ( current_window ) ;
671 }
672
673 /*
674  * Recalculates current menu's box size
675  */
676 void fghCalculateMenuBoxSize( void )
677 {
678   SFG_MenuEntry* menuEntry;
679   int width = 0, height = 0;
680
681   /*
682    * Make sure there is a current menu set
683    */
684   freeglut_assert_ready; freeglut_return_if_fail( fgStructure.Menu != NULL );
685
686   /*
687    * The menu's box size depends on the menu entries:
688    */
689   for( menuEntry = (SFG_MenuEntry *)fgStructure.Menu->Entries.First; menuEntry;
690        menuEntry = (SFG_MenuEntry *)menuEntry->Node.Next)
691   {
692     /*
693      * Update the menu entry's width value
694      */
695     menuEntry->Width = glutBitmapLength( FREEGLUT_MENU_FONT, menuEntry->Text );
696
697     /*
698      * If the entry is a submenu, then it needs to be wider to accomodate the arrow. JCJ 31 July 2003
699      */
700
701     if (menuEntry->SubMenu != NULL)
702        menuEntry->Width += glutBitmapLength( FREEGLUT_MENU_FONT, "_" );
703
704     /*
705      * Check if it's the biggest we've found
706      */
707     if( menuEntry->Width > width )
708       width = menuEntry->Width;
709
710     height += FREEGLUT_MENU_HEIGHT;
711   }
712
713   /*
714    * Store the menu's box size now:
715    */
716   fgStructure.Menu->Height = height + 2 * FREEGLUT_MENU_BORDER ; 
717   fgStructure.Menu->Width  = width  + 4 * FREEGLUT_MENU_BORDER ;
718 }
719
720
721 /* -- INTERFACE FUNCTIONS -------------------------------------------------- */
722
723 /*
724  * Creates a new menu object, adding it to the freeglut structure
725  */
726 int FGAPIENTRY glutCreateMenu( void (* callback)( int ) )
727 {
728     /*
729      * The menu object creation code resides in freeglut_structure.c
730      */
731     return( fgCreateMenu( callback )->ID );
732 }
733
734 /*
735  * Destroys a menu object, removing all references to it
736  */
737 void FGAPIENTRY glutDestroyMenu( int menuID )
738 {
739     SFG_Menu* menu = fgMenuByID( menuID );
740
741     freeglut_assert_ready; freeglut_return_if_fail( menu != NULL );
742
743     /*
744      * The menu object destruction code resides in freeglut_structure.c
745      */
746     fgDestroyMenu( menu );
747 }
748
749 /*
750  * Returns the ID number of the currently active menu
751  */
752 int FGAPIENTRY glutGetMenu( void )
753 {
754     freeglut_assert_ready;
755
756     /*
757      * Is there a current menu set?
758      */
759     if( fgStructure.Menu != NULL )
760     {
761         /*
762          * Yes, there is indeed...
763          */
764         return( fgStructure.Menu->ID );
765     }
766
767     /*
768      * No, there is no current menu at all
769      */
770     return( 0 );
771 }
772
773 /*
774  * Sets the current menu given its menu ID
775  */
776 void FGAPIENTRY glutSetMenu( int menuID )
777 {
778     SFG_Menu* menu = fgMenuByID( menuID );
779
780     freeglut_assert_ready; freeglut_return_if_fail( menu != NULL );
781
782     /*
783      * The current menu pointer is stored in fgStructure.Menu
784      */
785     fgStructure.Menu = menu;
786 }
787
788 /*
789  * Adds a menu entry to the bottom of the current menu
790  */
791 void FGAPIENTRY glutAddMenuEntry( const char* label, int value )
792 {
793     SFG_MenuEntry* menuEntry = (SFG_MenuEntry *)calloc( sizeof(SFG_MenuEntry), 1 );
794
795     /*
796      * Make sure there is a current menu set
797      */
798     freeglut_assert_ready; freeglut_return_if_fail( fgStructure.Menu != NULL );
799
800     /*
801      * Fill in the appropriate values...
802      */
803     menuEntry->Text = strdup( label );
804     menuEntry->ID   = value;
805
806     /*
807      * Have the new menu entry attached to the current menu
808      */
809     fgListAppend( &fgStructure.Menu->Entries, &menuEntry->Node );
810
811     /*
812      * Update the menu's dimensions now
813      */
814     fghCalculateMenuBoxSize();
815 }
816
817 /*
818  * Add a sub menu to the bottom of the current menu
819  */
820 void FGAPIENTRY glutAddSubMenu( const char* label, int subMenuID )
821 {
822   SFG_MenuEntry* menuEntry = (SFG_MenuEntry *)calloc( sizeof(SFG_MenuEntry), 1 );
823   SFG_Menu*      subMenu = fgMenuByID( subMenuID );
824
825   /*
826    * Make sure there is a current menu and the sub menu
827    * we want to attach actually exists...
828    */
829   freeglut_assert_ready; freeglut_return_if_fail( fgStructure.Menu != NULL );
830   freeglut_return_if_fail( subMenu != NULL );
831
832   /*
833    * Fill in the appropriate values
834    */
835   menuEntry->Text = strdup( label );
836   menuEntry->SubMenu = subMenu;
837   menuEntry->ID      = -1;
838
839   /*
840    * Make the submenu's parent window be the menu's parent window
841    */
842   fghSetSubmenuParentWindow ( fgStructure.Menu->ParentWindow, subMenu ) ;
843
844   /*
845    * Have the new menu entry attached to the current menu
846    */
847   fgListAppend( &fgStructure.Menu->Entries, &menuEntry->Node );
848
849   /*
850    * Update the menu's dimensions now
851    */
852   fghCalculateMenuBoxSize();
853 }
854
855 /*
856  * Changes the specified menu item in the current menu into a menu entry
857  */
858 void FGAPIENTRY glutChangeToMenuEntry( int item, const char* label, int value )
859 {
860     SFG_MenuEntry* menuEntry = NULL;
861
862     /*
863      * Make sure there is a current menu set...
864      */
865     freeglut_assert_ready; freeglut_return_if_fail( fgStructure.Menu != NULL );
866
867     /*
868      * Get n-th menu entry in the current menu, starting from one:
869      */
870     menuEntry = fghFindMenuEntry( fgStructure.Menu, item );
871
872     /*
873      * Make sure the menu entry exists
874      */
875     freeglut_return_if_fail( menuEntry != NULL );
876
877     /*
878      * We want it to become a normal menu entry, so:
879      */
880     if( menuEntry->Text != NULL )
881         free( menuEntry->Text );
882
883     menuEntry->Text = strdup( label );
884     menuEntry->ID      = value;
885     menuEntry->SubMenu = NULL;
886
887     /*
888      * Update the menu's dimensions now
889      */
890     fghCalculateMenuBoxSize();
891 }
892
893 /*
894  * Changes the specified menu item in the current menu into a sub-menu trigger.
895  */
896 void FGAPIENTRY glutChangeToSubMenu( int item, const char* label, int subMenuID )
897 {
898     SFG_Menu*      subMenu = fgMenuByID( subMenuID );
899     SFG_MenuEntry* menuEntry = NULL;
900
901     /*
902      * Make sure there is a current menu set and the sub menu exists...
903      */
904     freeglut_assert_ready; freeglut_return_if_fail( fgStructure.Menu != NULL );
905     freeglut_return_if_fail( subMenu != NULL );
906
907     /*
908      * Get n-th menu entry in the current menu, starting from one:
909      */
910     menuEntry = fghFindMenuEntry( fgStructure.Menu, item );
911
912     /*
913      * Make sure the menu entry exists
914      */
915     freeglut_return_if_fail( menuEntry != NULL );
916
917     /*
918      * We want it to become a sub menu entry, so:
919      */
920     if( menuEntry->Text != NULL )
921         free( menuEntry->Text );
922
923     menuEntry->Text = strdup( label );
924     menuEntry->SubMenu = subMenu;
925     menuEntry->ID      = -1;
926
927     /*
928      * Update the menu's dimensions now
929      */
930     fghCalculateMenuBoxSize();
931 }
932
933 /*
934  * Removes the specified menu item from the current menu
935  */
936 void FGAPIENTRY glutRemoveMenuItem( int item )
937 {
938     SFG_MenuEntry* menuEntry;
939
940     /*
941      * Make sure there is a current menu set
942      */
943     freeglut_assert_ready; freeglut_return_if_fail( fgStructure.Menu != NULL );
944
945     /*
946      * Get n-th menu entry in the current menu, starting from one:
947      */
948     menuEntry = fghFindMenuEntry( fgStructure.Menu, item );
949
950     /*
951      * Make sure the menu entry exists
952      */
953     freeglut_return_if_fail( menuEntry != NULL );
954
955     /*
956      * Removing a menu entry is quite simple...
957      */
958     fgListRemove( &fgStructure.Menu->Entries, &menuEntry->Node );
959
960     /*
961      * Free the entry label string, too
962      */
963     free( menuEntry->Text );
964
965     free( menuEntry );
966
967     /*
968      * Update the menu's dimensions now
969      */
970     fghCalculateMenuBoxSize();
971 }
972
973 /*
974  * Attaches a menu to the current window
975  */
976 void FGAPIENTRY glutAttachMenu( int button )
977 {
978     freeglut_assert_ready;
979
980     /*
981      * There must be a current window and a current menu set:
982      */
983     freeglut_return_if_fail( fgStructure.Window != NULL || fgStructure.Menu != NULL );
984
985     /*
986      * Make sure the button value is valid (0, 1 or 2, see freeglut.h)
987      */
988     freeglut_return_if_fail( button == GLUT_LEFT_BUTTON || button == GLUT_MIDDLE_BUTTON || button == GLUT_RIGHT_BUTTON );
989
990     /*
991      * It is safe now to attach the menu
992      */
993     fgStructure.Window->Menu[ button ] = fgStructure.Menu;
994
995     /*
996      * Make the parent window of the menu (and all submenus) the current window
997      */
998     fghSetSubmenuParentWindow ( fgStructure.Window, fgStructure.Menu ) ;
999 }
1000
1001 /*
1002  * Detaches a menu from the current window
1003  */
1004 void FGAPIENTRY glutDetachMenu( int button )
1005 {
1006     freeglut_assert_ready;
1007
1008     /*
1009      * There must be a current window set:
1010      */
1011     freeglut_return_if_fail( fgStructure.Window != NULL );
1012
1013     /*
1014      * Make sure the button value is valid (0, 1 or 2, see freeglut.h)
1015      */
1016     freeglut_return_if_fail( button != 0 && button != 1 && button != 2 );
1017
1018     /*
1019      * It is safe now to detach the menu
1020      */
1021     fgStructure.Window->Menu[ button ] = NULL;
1022 }
1023
1024 /*
1025  * A.Donev: Set and retrieve the menu's user data
1026  */
1027 void* FGAPIENTRY glutGetMenuData( void )
1028 {
1029    return(fgStructure.Menu->UserData);
1030 }
1031
1032 void FGAPIENTRY glutSetMenuData(void* data)
1033 {
1034   fgStructure.Menu->UserData=data;
1035 }
1036
1037 /*** END OF FILE ***/