4 * Pull-down menu creation and handling.
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
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:
17 * The above copyright notice and this permission notice shall be included
18 * in all copies or substantial portions of the Software.
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.
32 #define G_LOG_DOMAIN "freeglut-menu"
34 #include "../include/GL/freeglut.h"
35 #include "../include/GL/freeglut_internal.h"
38 * TODO BEFORE THE STABLE RELEASE:
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.
45 /* -- DEFINITIONS ---------------------------------------------------------- */
48 * We'll be using freeglut fonts to draw the menu
50 #define FREEGLUT_MENU_FONT GLUT_BITMAP_8_BY_13
51 #define FREEGLUT_MENU_HEIGHT 15
54 /* -- PRIVATE FUNCTIONS ---------------------------------------------------- */
57 * Private static function to check for the current menu/sub menu activity state
59 static gboolean fghCheckMenuStatus( SFG_Menu* menu )
61 SFG_Window* window = fgStructure.Window;
65 * First of all check any of the active sub menus...
67 for( i=0; i<(gint) g_list_length( menu->Entries ); i++ )
69 SFG_MenuEntry* menuEntry = (SFG_MenuEntry *) g_list_nth( menu->Entries, i )->data;
72 * Is that an active sub menu by any case?
74 if( menuEntry->SubMenu != NULL && menuEntry->IsActive == TRUE )
77 * OKi, have the sub-menu checked, too. If it returns TRUE, it will mean
78 * that it caught the mouse cursor and we do not need to regenerate
79 * the activity list, and so our parents do...
81 if( fghCheckMenuStatus( menuEntry->SubMenu ) == TRUE )
87 * That much about our sub menus, let's get to checking the current menu:
89 x = window->State.MouseX - menu->X;
90 y = window->State.MouseY - menu->Y;
93 * Mark all menu entries inactive...
95 for( i=0; i<(gint) g_list_length( menu->Entries ); i++ )
97 SFG_MenuEntry* menuEntry = (SFG_MenuEntry *) g_list_nth( menu->Entries, i )->data;
99 menuEntry->IsActive = FALSE;
102 menu->IsActive = FALSE;
105 * Check if the mouse cursor is contained within the current menu box
107 if( x >= 0 && x < menu->Width && y >= 0 && y < menu->Height )
110 * Calculation of the highlighted menu item is easy enough now:
112 gint menuID = y / FREEGLUT_MENU_HEIGHT;
115 * The mouse cursor is somewhere over our box, check it out.
117 SFG_MenuEntry* menuEntry = (SFG_MenuEntry *) g_list_nth( menu->Entries, menuID )->data;
118 g_assert( menuEntry != NULL );
121 * Mark the menu as active...
123 menuEntry->IsActive = TRUE;
124 menuEntry->Ordinal = menuID;
127 * Don't forget about marking the current menu as active, too:
129 menu->IsActive = TRUE;
132 * OKi, we have marked that entry as active, but it would be also
133 * nice to have it's contents updated, in case it's a sub menu.
134 * Also, ignore the return value of the check function:
136 if( menuEntry->SubMenu != NULL )
138 gint x = window->State.MouseX;
139 gint y = window->State.MouseY;
142 * Set up the initial menu position now...
145 if( x > 15 ) menuEntry->SubMenu->X = x - 15; else menuEntry->SubMenu->X = 15;
146 if( y > 15 ) menuEntry->SubMenu->Y = y - 15; else menuEntry->SubMenu->Y = 15;
148 if( x > (glutGet( GLUT_WINDOW_WIDTH ) - menuEntry->SubMenu->Width - 15) )
149 menuEntry->SubMenu->X = glutGet( GLUT_WINDOW_WIDTH ) - menuEntry->SubMenu->Width - 15;
150 if( y > (glutGet( GLUT_WINDOW_HEIGHT ) - menuEntry->SubMenu->Height - 15) )
151 menuEntry->SubMenu->Y = glutGet( GLUT_WINDOW_HEIGHT ) - menuEntry->SubMenu->Height - 15;
154 * ...then check the submenu's state:
156 fghCheckMenuStatus( menuEntry->SubMenu );
160 * Report back that we have caught the menu cursor
166 * Looks like the menu cursor is somewhere else...
172 * Displays a menu box and all of it's submenus (if they are active)
174 static void fghDisplayMenuBox( SFG_Menu* menu )
176 SFG_Window* window = fgStructure.Window;
180 * Have the menu box drawn first. The +- values are
181 * here just to make it more nice-looking...
183 glColor4f( 0.0, 0.0, 0.0, 1.0 );
185 glVertex2f( menu->X - 8 , menu->Y - 1 );
186 glVertex2f( menu->X + 8 + menu->Width, menu->Y - 1 );
187 glVertex2f( menu->X + 8 + menu->Width, menu->Y + 4 + menu->Height );
188 glVertex2f( menu->X - 8 , menu->Y + 4 + menu->Height );
191 glColor4f( 0.3, 0.4, 0.5, 1.0 );
193 glVertex2f( menu->X - 6 , menu->Y + 1 );
194 glVertex2f( menu->X + 6 + menu->Width, menu->Y + 1 );
195 glVertex2f( menu->X + 6 + menu->Width, menu->Y + 2 + menu->Height );
196 glVertex2f( menu->X - 6 , menu->Y + 2 + menu->Height );
200 * Check if any of the submenus is currently active...
202 for( i=0; i<(gint) g_list_length( menu->Entries ); i++ )
204 SFG_MenuEntry* menuEntry = (SFG_MenuEntry *) g_list_nth( menu->Entries, i )->data;
207 * Has the menu been marked as active, maybe?
209 if( menuEntry->IsActive == TRUE )
212 * That's truly right, and we need to have it highlighted.
213 * There is an assumption that mouse cursor didn't move
214 * since the last check of menu activity state:
216 gint menuID = menuEntry->Ordinal;
219 * So have the highlight drawn...
221 glColor4f( 0.2, 0.3, 0.4, 1.0 );
223 glVertex2f( menu->X - 6 , menu->Y + (menuID + 0)*FREEGLUT_MENU_HEIGHT + 1 );
224 glVertex2f( menu->X + 6 + menu->Width, menu->Y + (menuID + 0)*FREEGLUT_MENU_HEIGHT + 1 );
225 glVertex2f( menu->X + 6 + menu->Width, menu->Y + (menuID + 1)*FREEGLUT_MENU_HEIGHT + 2 );
226 glVertex2f( menu->X - 6 , menu->Y + (menuID + 1)*FREEGLUT_MENU_HEIGHT + 2 );
232 * Print the menu entries now...
234 glColor4f( 1, 1, 1, 1 );
236 for( i=0; i<(gint) g_list_length( menu->Entries ); i++ )
238 SFG_MenuEntry* menuEntry = (SFG_MenuEntry *) g_list_nth( menu->Entries, i )->data;
241 * Move the raster into position...
245 menu->Y + (i + 1)*FREEGLUT_MENU_HEIGHT
249 * Have the label drawn, character after character:
251 for( j=0; j<menuEntry->Text->len; j++ )
252 glutBitmapCharacter( FREEGLUT_MENU_FONT, (gint) menuEntry->Text->str[ j ] );
256 * Now we are ready to check if any of our children needs to be redrawn:
258 for( i=0; i<(gint) g_list_length( menu->Entries ); i++ )
260 SFG_MenuEntry* menuEntry = (SFG_MenuEntry *) g_list_nth( menu->Entries, i )->data;
263 * Is that an active sub menu by any case?
265 if( menuEntry->SubMenu != NULL && menuEntry->IsActive == TRUE )
268 * Yeah, indeed. Have it redrawn now:
270 fghDisplayMenuBox( menuEntry->SubMenu );
276 * Displays the currently active menu for the current window
278 void fgDisplayMenu( void )
280 SFG_Window* window = fgStructure.Window;
281 SFG_Menu* menu = NULL;
285 * Make sure there is a current window available
287 freeglut_assert_window;
290 * Check if there is an active menu attached to this window...
292 for( i=0; i<FREEGLUT_MAX_MENUS; i++ )
294 if( window->Menu[ i ] != NULL && window->MenuActive[ i ] == TRUE )
295 menu = window->Menu[ i ];
299 * Did we find an active window?
301 freeglut_return_if_fail( menu != NULL );
304 * Prepare the OpenGL state to do the rendering first:
306 glPushAttrib( GL_DEPTH_BUFFER_BIT | GL_TEXTURE_BIT | GL_LIGHTING_BIT | GL_POLYGON_BIT );
308 glDisable( GL_DEPTH_TEST );
309 glDisable( GL_TEXTURE_2D );
310 glDisable( GL_LIGHTING );
311 glDisable( GL_CULL_FACE );
314 * We'll use an orthogonal projection matrix to draw the menu:
316 glMatrixMode( GL_PROJECTION );
320 0, glutGet( GLUT_WINDOW_WIDTH ),
321 glutGet( GLUT_WINDOW_HEIGHT ), 0,
326 * Model-view matix gets reset to identity:
328 glMatrixMode( GL_MODELVIEW );
333 * First of all, have the exact menu status check:
335 fghCheckMenuStatus( menu );
338 * The status has been updated and we're ready to have the menu drawn now:
340 fghDisplayMenuBox( menu );
343 * Restore the old OpenGL settings now
347 glMatrixMode( GL_MODELVIEW );
349 glMatrixMode( GL_PROJECTION );
354 * Activates a menu pointed by the function argument
356 void fgActivateMenu( gint button )
358 SFG_Window* window = fgStructure.Window;
359 SFG_Menu* menu = NULL;
362 freeglut_assert_window;
365 * Mark the menu as active, so that it gets displayed:
367 window->MenuActive[ button ] = TRUE;
370 * We'll be referencing this menu a lot, so remember it's address:
372 menu = window->Menu[ button ];
375 * Grab the mouse cursor position respective to the current window
377 x = window->State.MouseX;
378 y = window->State.MouseY;
381 * Set up the initial menu position now:
383 if( x > 10 ) menu->X = x - 10; else menu->X = 5;
384 if( y > 10 ) menu->Y = y - 10; else menu->Y = 5;
386 if( x > (glutGet( GLUT_WINDOW_WIDTH ) - menu->Width ) )
387 menu->X = glutGet( GLUT_WINDOW_WIDTH ) - menu->Width;
388 if( y > (glutGet( GLUT_WINDOW_HEIGHT ) - menu->Height) )
389 menu->Y = glutGet( GLUT_WINDOW_HEIGHT ) - menu->Height;
393 * Private static function to check for menu entry selection on menu deactivation
395 static void fghCheckMenuSelect( SFG_Menu* menu )
400 * First of all check any of the active sub menus...
402 for( i=0; i<(gint) g_list_length( menu->Entries ); i++ )
404 SFG_MenuEntry* menuEntry = (SFG_MenuEntry *) g_list_nth( menu->Entries, i )->data;
407 * Is this menu entry active?
409 if( menuEntry->IsActive == TRUE )
412 * If this is not a sub menu, execute the menu callback and return...
414 if( menuEntry->SubMenu == NULL )
417 * ...certainly given that there is one...
419 if( menu->Callback != NULL )
420 menu->Callback( menuEntry->ID );
426 * Otherwise recurse into the submenu.
428 fghCheckMenuSelect( menuEntry->SubMenu );
431 * There is little sense in dwelling the search on
439 * Deactivates a menu pointed by the function argument.
441 void fgDeactivateMenu( gint button )
443 SFG_Window* window = fgStructure.Window;
444 SFG_Menu* menu = NULL;
448 * Make sure there is a current window available...
450 freeglut_assert_window;
453 * Check if there is an active menu attached to this window...
455 for( i=0; i<FREEGLUT_MAX_MENUS; i++ )
457 if( window->Menu[ i ] != NULL && window->MenuActive[ i ] == TRUE )
458 menu = window->Menu[ i ];
462 * Did we find an active window?
464 freeglut_return_if_fail( menu != NULL );
467 * Check if there was any menu entry active. This would
468 * mean the user has selected a menu entry...
470 fghCheckMenuSelect( menu );
473 * Forget about having that menu active anymore, now:
475 fgStructure.Window->MenuActive[ button ] = FALSE;
479 * Recalculates current menu's box size
481 void fghCalculateMenuBoxSize( void )
486 * Make sure there is a current menu set
488 freeglut_assert_ready; freeglut_return_if_fail( fgStructure.Menu != NULL );
491 * The menu's box size depends on the menu entries:
493 for( i=0, width=0; i<(gint) g_list_length( fgStructure.Menu->Entries ); i++ )
495 SFG_MenuEntry* menuEntry = (SFG_MenuEntry *) g_list_nth( fgStructure.Menu->Entries, i )->data;
498 * Update the menu entry's width value
500 menuEntry->Width = glutBitmapLength( FREEGLUT_MENU_FONT, menuEntry->Text->str );
503 * Check if it's the biggest we've found
505 if( menuEntry->Width > width )
506 width = menuEntry->Width;
510 * Store the menu's box size now:
512 fgStructure.Menu->Height = i * FREEGLUT_MENU_HEIGHT;
513 fgStructure.Menu->Width = width;
517 /* -- INTERFACE FUNCTIONS -------------------------------------------------- */
520 * Creates a new menu object, adding it to the freeglut structure
522 int FGAPIENTRY glutCreateMenu( void (* callback)( int ) )
525 * The menu object creation code resides in freeglut_structure.c
527 return( fgCreateMenu( callback )->ID );
531 * Destroys a menu object, removing all references to it
533 void FGAPIENTRY glutDestroyMenu( int menuID )
535 SFG_Menu* menu = fgMenuByID( menuID );
537 freeglut_assert_ready; freeglut_return_if_fail( menu != NULL );
540 * The menu object destruction code resides in freeglut_structure.c
542 fgDestroyMenu( menu );
546 * Returns the ID number of the currently active menu
548 int FGAPIENTRY glutGetMenu( void )
550 freeglut_assert_ready;
553 * Is there a current menu set?
555 if( fgStructure.Menu != NULL )
558 * Yes, there is indeed...
560 return( fgStructure.Menu->ID );
564 * No, there is no current menu at all
570 * Sets the current menu given it's menu ID
572 void FGAPIENTRY glutSetMenu( int menuID )
574 SFG_Menu* menu = fgMenuByID( menuID );
576 freeglut_assert_ready; freeglut_return_if_fail( menu != NULL );
579 * The current menu pointer is stored in fgStructure.Menu
581 fgStructure.Menu = menu;
585 * Adds a menu entry to the bottom of the current menu
587 void FGAPIENTRY glutAddMenuEntry( const char* label, int value )
589 SFG_MenuEntry* menuEntry = g_new0( SFG_MenuEntry, 1 );
592 * Make sure there is a current menu set
594 freeglut_assert_ready; freeglut_return_if_fail( fgStructure.Menu != NULL );
597 * Fill in the appropriate values...
599 menuEntry->Text = g_string_new( label );
600 menuEntry->ID = value;
603 * Have the new menu entry attached to the current menu
605 fgStructure.Menu->Entries = g_list_append( fgStructure.Menu->Entries, menuEntry );
608 * Update the menu's dimensions now
610 fghCalculateMenuBoxSize();
614 * Add a sub menu to the bottom of the current menu
616 void FGAPIENTRY glutAddSubMenu( const char* label, int subMenuID )
618 SFG_MenuEntry* menuEntry = g_new0( SFG_MenuEntry, 1 );
619 SFG_Menu* subMenu = fgMenuByID( subMenuID );
622 * Make sure there is a current menu and the sub menu
623 * we want to attach actually exists...
625 freeglut_assert_ready; freeglut_return_if_fail( fgStructure.Menu != NULL );
626 freeglut_return_if_fail( subMenu != NULL );
629 * Fill in the appropriate values
631 menuEntry->Text = g_string_new( label );
632 menuEntry->SubMenu = subMenu;
636 * Have the new menu entry attached to the current menu
638 fgStructure.Menu->Entries = g_list_append( fgStructure.Menu->Entries, menuEntry );
641 * Update the menu's dimensions now
643 fghCalculateMenuBoxSize();
647 * Changes the specified menu item in the current menu into a menu entry
649 void FGAPIENTRY glutChangeToMenuEntry( int item, const char* label, int value )
651 SFG_MenuEntry* menuEntry = NULL;
654 * Make sure there is a current menu set...
656 freeglut_assert_ready; freeglut_return_if_fail( fgStructure.Menu != NULL );
659 * Make sure the item counter seems valid
661 freeglut_return_if_fail( (item > 0) && (item <= (gint) g_list_length( fgStructure.Menu->Entries ) ) );
664 * Get n-th menu entry in the current menu, starting from one:
666 menuEntry = (SFG_MenuEntry *) g_list_nth( fgStructure.Menu->Entries, item - 1 )->data;
669 * We want it to become a normal menu entry, so:
671 if( menuEntry->Text != NULL )
672 g_string_free( menuEntry->Text, TRUE );
674 menuEntry->Text = g_string_new( label );
675 menuEntry->ID = value;
676 menuEntry->SubMenu = NULL;
679 * Update the menu's dimensions now
681 fghCalculateMenuBoxSize();
685 * Changes the specified menu item in the current menu into a sub-menu trigger.
687 void FGAPIENTRY glutChangeToSubMenu( int item, const char* label, int subMenuID )
689 SFG_Menu* subMenu = fgMenuByID( subMenuID );
690 SFG_MenuEntry* menuEntry = NULL;
693 * Make sure there is a current menu set and the sub menu exists...
695 freeglut_assert_ready; freeglut_return_if_fail( fgStructure.Menu != NULL );
696 freeglut_return_if_fail( subMenu != NULL );
699 * Make sure the item counter seems valid
701 freeglut_return_if_fail( (item > 0) && (item <= (gint) g_list_length( fgStructure.Menu->Entries ) ) );
704 * Get n-th menu entry in the current menu, starting from one:
706 menuEntry = (SFG_MenuEntry *) g_list_nth( fgStructure.Menu->Entries, item - 1 )->data;
709 * We want it to become a sub menu entry, so:
711 if( menuEntry->Text != NULL )
712 g_string_free( menuEntry->Text, TRUE );
714 menuEntry->Text = g_string_new( label );
715 menuEntry->SubMenu = subMenu;
719 * Update the menu's dimensions now
721 fghCalculateMenuBoxSize();
725 * Removes the specified menu item from the current menu
727 void FGAPIENTRY glutRemoveMenuItem( int item )
729 SFG_MenuEntry* menuEntry;
732 * Make sure there is a current menu set
734 freeglut_assert_ready; freeglut_return_if_fail( fgStructure.Menu != NULL );
737 * Make sure the item counter seems valid
739 freeglut_return_if_fail( (item > 0) && (item <= (gint) g_list_length( fgStructure.Menu->Entries ) ) );
742 * Removing a menu entry is quite simple...
744 menuEntry = (SFG_MenuEntry *) g_list_nth( fgStructure.Menu->Entries, item - 1 )->data;
746 fgStructure.Menu->Entries = g_list_remove(
747 fgStructure.Menu->Entries,
752 * Free the entry label string, too
754 g_string_free( menuEntry->Text, TRUE );
757 * Update the menu's dimensions now
759 fghCalculateMenuBoxSize();
763 * Attaches a menu to the current window
765 void FGAPIENTRY glutAttachMenu( int button )
767 freeglut_assert_ready;
770 * There must be a current window and a current menu set:
772 freeglut_return_if_fail( fgStructure.Window != NULL || fgStructure.Menu != NULL );
775 * Make sure the button value is valid (0, 1 or 2, see freeglut.h)
777 freeglut_return_if_fail( button == GLUT_LEFT_BUTTON || button == GLUT_MIDDLE_BUTTON || button == GLUT_RIGHT_BUTTON );
780 * It is safe now to attach the menu
782 fgStructure.Window->Menu[ button ] = fgStructure.Menu;
786 * Detaches a menu from the current window
788 void FGAPIENTRY glutDetachMenu( int button )
790 freeglut_assert_ready;
793 * There must be a current window set:
795 freeglut_return_if_fail( fgStructure.Window != NULL );
798 * Make sure the button value is valid (0, 1 or 2, see freeglut.h)
800 freeglut_return_if_fail( button != 0 && button != 1 && button != 2 );
803 * It is safe now to detach the menu
805 fgStructure.Window->Menu[ button ] = NULL;
808 /*** END OF FILE ***/