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